[STC单片机] 51单片机入门经验分享

[复制链接]
4953|11
 楼主| dabing89 发表于 2018-9-17 10:12 | 显示全部楼层 |阅读模式
本帖最后由 dabing89 于 2018-9-19 15:50 编辑

      学单片机也好长时间了,走过许多弯路,挖过许多坑,浪费了很多时间,做过实际产品后回顾过去的学习经历,发现很多坑其实是可以避免的,单片机的入门应该可以更轻松一点,借21IC一方宝地,写写我的一些学习经历,希望对刚入门甚至还没有入门的新手有所帮助,心愿足矣,本人水平有限,求老手轻拍,有错请指出,有事请留言。。。
      单片机的学习,模电数电是最基本的,也是最重要的,构成单片机最小系统的三个要素,是电源,晶振和复位电路,话说这是之前的STC89C52了,现在的STC15系列,晶振和复位电路都内置了,但是电源还是要有的我们就以这2者做对比来学习,看看有何不一样的地方。


      关于单片机的电源
通过查看单片机的型号可知,STC89C52RC的供电电压范围在3.3V-5.5V之间,STC15W1K16PWM这款芯片的供电电压范围在2.4V-5.5V之间,他们都可以用5V供电,后者可以用3.3V来供电,STC89C52RC这块片之所以还没有退役,还是因为学习者大多以他作为学习入门,在实际项目中,我只见到过一次,某天,老板从库房里拿出2管STC89C52,大概几十片,问我还能不能用上,我只呵呵了一下
        言归正传,我们既然确定用5V来供电,那么电源是如何产生呢?翻开模电课本的514页,我们知道可以用变压器来搞定,比如在TB上搜220V转12V变压器12W,就搜到了,变压器直接输出的是交流电12V,我们没法用啊,于是还需要整流,滤波,稳压等等,整流需要二极管,这里我们选择桥式整流,选择4只1N4007,规格1A 1000V就好了,这个1A指的是最大整流电流,这个1000V指的是最高反向工作电压,这些在模电课本520页都有的,这样经过桥式整流之后,输出的电压是脉动的直流电压,已经有正负极性了,但是我们还是不能用,交流成分实在是太大,因此还需要加一个容量稍大些的电解电容,比如1000UF/35V,电容的耐压值的选取应该大于1.1*1.414*12,电网电压也是波动的,预留出10%就好了,根据电容容量计算公式,计算出容量在1000UF-2000UF是合适的,12W的变压器,我们只要输出500MA的电流就够了,这样的设计是完全满足我们的需要的。电路图如下:


图中的C1,C3容量较大,作用是用于滤波,C2是消除自激震荡,高频干扰,C4也是消除噪声等高频干扰,三端稳压芯片用的是LM7805,非常常见的电路了,后面加了一个TVS,这个在实际项目产品中是必须要加的,瞬态抑制二极管,如果电压高于5V,会瞬间动作,保护后级电路,带CA的是没有方向的,直接插上就可使用,后面用了个LED红色的小灯,我们知道小灯就是一个二极管,不过发光二极管和普通的二极管的压降是不一样,像这种红色的,一般压降在1.8-2.0V可以正常工作,电流在4-20MA,如果超出这个数值,基本就烧坏了,R1是限流电阻,限制LED的电流,我选择5MA的工作电流,5/0.005 = 1K欧姆,就是这么来的,这样,电源部分就搞明白了,我们获取了5V的电源。


       关于晶振
      晶振,顾名思义,晶体振荡器,这就好比人的心脏啊,提供整齐划一的节拍,如果这个东西不准,或者不起振,单片机玩不转的,在STC89C52上,是必须要加晶振电路的,如图所示这样:     
2个20PF的电容是帮助晶振起震的,维持信号稳定,Y1是无源晶振,啥叫无源呢?指的是没有震荡源,也就是说他自己单独玩不转的,需要内部的RC电路配合,产生一个十分稳定的时钟信号源,跟无源相对就是有源晶振了,这个价格相对来说贵一些,一般4个引脚,VCC,GND,OUT,NC,NC是直接悬空的,当给有源晶振供电以后,再其OUT端,会产生一个方波信号,直接接在XTAL1上就好,XTAL2可以悬空不接,当然这里是指STC89C52,我记得之前用STM32F103RCT6这款芯片做产品电磁兼容测试,因为有源晶振辐射超标,再加上电路布局不合理,过不了电磁兼容,没有办法,只好把晶振搞掉了,但是就算晶振换下来了,这个引脚还是可以造成干扰的,于是就配置成普通IO,然后接电阻下拉接地才过了,这都是经验教训啊。

      关于复位电路
复位电路,是用来在单片机执行异常的时候,可以让他从头开始执行程序,STC89C52RC这款芯片是高电平复位,低电平正常工作,电路图如下所示:

来解析下这个电路图,先来看STC89C52RC,我们知道电容的特性是通交隔直,电容是个储能元件,储存的是电场的能量,在没有电到上电的瞬间,电容肯定要充电的,这个时候电容就短路成一根导线了,RST端就被拉到了5V,执行复位指令,当电容充满电后,会断开连接,RST引脚会被10K电阻下拉到低电平,所以电路开始正常的工作,之所以接上按键以及100R的电阻,是可以手动复位,当按键按下的时候,RC并联,我们知道,电阻是消耗电能的,会瞬间把电容里面的电能释放掉,具体的时间计算,可以看电路基础第4版,127页,一阶电路的零输入响应,计算比较麻烦,有一个时间参数,T = RC(念TAO,不会打),单位是秒,T的大小反映了一阶电路过渡过程的进展速度。我们只要知道会瞬间释放就好了,手动复位之后,电容又开始了充电断开正常工作的过程,就是这样。51单片机和STM32不同,STM32是低电平复位,高电平正常工作,这儿原理是一样的。
      好了,单片机学习最基础的部分,已经了解了,接下来,就可以动手干点别的事情了。。。未完待续。。。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| dabing89 发表于 2018-9-17 15:12 | 显示全部楼层
本帖最后由 dabing89 于 2018-9-19 15:32 编辑

接上篇帖子,单片机这种东西,如果是用来考试,翻翻书大概就可以了,如果是拿来开发一些小的作品,自学2个月,也不会太难,因为现在开发板也是很多的,资料也很丰富,如果你是想找工作,想独立开发产品,这个,对于一个刚毕业的人来说,就比较难了,不吹不黑,我见过很多半途而废的人,因为学习走到了死胡同,再也走不出来了,其中有一个是我们另一个部门一个部长,模电数电基础还可以,自学了几年,就是不入门,后来我教他2个星期,带他入门了,所以并不是说单片机入门有多么难,很大的原因,是走了弯路,而且沿着弯路一直走了下去。我们希望分享一些有用的东西,实用的东西,让新手少走些弯路,如果每个坑都重踩一遍,这得浪费多少时间啊。
    言归正传,单片机是必须要亲自动手敲代码才能学会的,所以从这一章节开始,我们会分享一些例程,并详细解析,希望能给新手带来帮助,还是那句话,本人水平有限,老手轻拍,有错误请指出,有事请留言。
    一个经典的例程,点亮你的LED
我以手里的这块STC15W1K16PWM芯片为例来写例子,这块芯片至少还有一些人用来开发产品,学习了至少还有一定实用价值,如果你手里有同类型的芯片,那就更方便学习了,不管干什么,都是从简单到复杂的,比如学计算机,第一句就是“HELLO WORLD”,那么学习单片机,就是点亮你的LED了先把原理图上传,单片机是软硬结合的东西,硬件为主,软件为辅,所以需要看原理图编程。
   新建一个KEIL4工程,把下面代码敲进去,就可以实现LED的闪烁了,但是还是需要解释下一下代码。

   
  1. /*******************************************************************
  2. * 文件名  LED0 500MS闪烁
  3. * 描述:        点亮LED
  4. * 2018-09-17 调试通过
  5. * 功能  入门模板
  6. * 作者:大核桃
  7. * 版本号:V1.00(2018.09.17)
  8. ********************************************************************/
  9. #include "config.h"
  10. #include "intrins.h"


  11. /*******************************************************************
  12. * 文件名 变量重新定义区域
  13. * 描述:        
  14. * 功 能
  15. * 作者:大核桃
  16. * 版本号:V1.00(2018.09.17)
  17. ********************************************************************/

  18. typedef unsigned char uint8;//无符号字符型
  19. typedef unsigned int  uint16;//无符号整型
  20. typedef unsigned long uint32;//无符号长整型


  21. /*******************************************************************
  22. * 文件名:位重新定义区域 函数前置声明
  23. * 描述:        
  24. * 功 能
  25. * 作者:大核桃
  26. * 版本号:V1.00(2018.09.17)
  27. ********************************************************************/
  28. void Delay500ms();                //@11.0592MHz
  29. void MCU_Port_Init(void);


  30. sbit LED0 = P1^0;
  31. sbit LED1 = P1^1;
  32. sbit LED2 = P1^2;
  33. sbit LED3 = P1^3;
  34. sbit LED4 = P1^4;
  35. sbit LED5 = P3^2;
  36. sbit LED6 = P0^0;
  37. sbit LED7 = P0^1;


  38. /*******************************************************************
  39. * 文件名 main函数入口
  40. * 描述:        
  41. * 功 能
  42. * 作者:大核桃
  43. * 版本号:V1.00(2018.09.17)
  44. ********************************************************************/
  45. void main(void)
  46. {
  47.         MCU_Port_Init();//端口模式初始化函数
  48.         //上电IO默认是0
  49.         LED0 = 1;//输出1
  50.         LED1 = 0;
  51.         LED2 = 0;
  52.         LED3 = 0;
  53.         LED4 = 0;
  54.         LED5 = 0;
  55.         LED6 = 0;
  56.         LED7 = 0;//

  57.         while(1)
  58.         {
  59.              P2 = 0XFE;//1111_1110;
  60.                  Delay500ms();//500ms延时 11.0592MHZ
  61.                  P2 = 0XFF;//1111_1111;
  62.                  Delay500ms();//500ms延时 11.0592MHZ
  63.         }
  64. }

  65. /*******************************************************************
  66. * 文件名:void MCU_Port_Init(void)
  67. * 描述:        MCU端口上电初始化函数
  68. * 功 能
  69. * 作者:大核桃
  70. * 版本号:V1.00(2018.09.17)
  71. ********************************************************************/
  72. void MCU_Port_Init(void)
  73. {
  74.         //第0 和1位配置推完输出模式,大电流
  75.         P0M1 = 0XFC; //        1111_1100
  76.         P0M0 = 0X03; // 0000_0011

  77.         //第01234位配置推完输出模式,大电流,567配置高阻输入,用于ADC
  78.         P1M1 = 0XE0; //1110_0000        
  79.         P1M0 = 0X1F; //0001_1111

  80.         //P2配置位准双向口
  81.         P2M1 = 0X00; //0000_0000        
  82.         P2M0 = 0X00; //0000_0000

  83.         P2 = 0XFF;//P2口初始化为1

  84.         //P5配置位准双向口
  85.         P5M1 = 0X00; //0000_0000        
  86.         P5M0 = 0X00; //0000_0000

  87.         P5 = 0XFF;//P5口初始化为1

  88.         //P3 23467推完输出
  89.         P3M1 = 0X00; //0000_0000        
  90.         P3M0 = 0XFC; //1101_1100

  91.         P3 = 0X23;  //0010_0011
  92.                
  93. }
  94. /*******************************************************************
  95. * 文件名:void Delay500ms()
  96. * 描述:        通用延时函数
  97. * 功 能
  98. * 作者:大核桃
  99. * 版本号:V1.00(2018.09.17)
  100. ********************************************************************/
  101. void Delay500ms()                //@11.0592MHz
  102. {
  103.         unsigned char i, j, k;

  104.         _nop_();
  105.         _nop_();
  106.         i = 22;
  107.         j = 3;
  108.         k = 227;
  109.         do
  110.         {
  111.                 do
  112.                 {
  113.                         while (--k);
  114.                 } while (--j);
  115.         } while (--i);
  116. }


   头文件 #include "config.h"这个头文件是我们新建的,这里面是STC15W系列的寄存器地址定义等等,就好比学STC89C52RC那样,先包含头文件#include "reg52.h"一样,因为STC15W系列的不是标准的51,所以不能用REG52.H这个头文件,需要我们自己去新建一个,然后把官网的头文件复制过来就好了。
  typedef 的作用
typedef是用来声明新类型名的,也即是说我觉得unsigned char 太长了,记不住,或者每次都写很繁琐怎么办?另外起一个名字,typedef就是起到这个作用,而且用typedef定义过的是可以参与系统编译的,如果编译错误,那么编译器是提醒你的,如果你是用#define来定义,也可以,除非你保证自己的程序没有错误,如果出错,不小心把unsigned char写unsigned charr也是会通过的,因为#define知识简单的替换,并不参与系统编译。typedef的详细用法请参考C语言第四版326页,非常详细。
  
  关于无符号字符型的定义,无符号字符型占1个字节,取值范围在0-255之间,无符号基本整型占2个字节,取值范围是0-65535,但是在STM32上,我记得是unsigned short ,即无符号短整型,一直混用,当时搞得好混,后来看C语言,发现可以同时用,unsigned long,即无符号长整型,取值范围是0-4294967295,我们暂时就用到这3种类型的数据变量,至于更具体的请翻阅C语言第44页。
   关于函数前置声明
   函数如果在MAIN函数前面定义,是不要函数前置声明的,但是如果在MAIN后面定义,那就一定需要了,不然会报错的。
   sbit定义引脚
比如sbit LED0 = P1^0;,P1^0必须要大写而且必须要加一个分号,sbit定义是51单片机独有的,STM32就没有这个东西,直接初始化用就好了。
   main入口函数
   统一规定的,就这么个写法,第一个void指的是函数没有返回值,第二个void指的是函数没有形参调用,在STM32里面,就不是这样的,int main(void),是一个基本整型的变量作为返回值的。里面是一个while(1)的大循环,这没啥好说的。
  关于端口配置
   如果是用STC89C52的话,不需要端口配置的,除了P0是开漏输出,其他普通IO都是准双向口,直接赋值即可,但是STC89C52的IO驱动能力是有限的,LED小灯是一个耗电大户,必须要加一个三极管才可以,STC15W则不需要,IO可以配置位4种模式,准双向口,开漏输出,推挽输出,高阻输入4种模式,前2个没什么分别,推挽输出是大电流,可以直接驱动LED,最大可以达到20MA,但是根据使用经验,最好是灌入电流,也就是IO是0的时候,点亮LED,至于拉电流,IO上拉15MA可能就极限了。

程序中的寄存器定义P0M0 P0M1的配置,就是参照这个表格配置出来的,因为好多引脚还用在了别的上面,实现别的功能。请注意,P0M0和P0M1只是设定IO端口的工作类型,至于输出IO是低电平还是高电平,仍然是需要你自己决定的 ,不然达不到你要实现的效果。我们只是想点亮第一组LED的第一个灯,那么选择LED = 1;就可以了,然后在主循环中延时闪烁就好了。
   关于二进制和16进制
   我们在循环中,写P2 = 0XFE,这儿是将P2端口的最低位清零,换算成二进制也就是1111_1110,这个0就代表了P2口的最低位,也就是原理图中的DB0,如果我们不想并口操作IO,可以用sbit DB0 = P2^0;这样定义就可以将P2 = 0XFE,替换成DB = 0;P2 = 0XFF;替换DB0 = 1;就可以实现同样的效果,我们称这样的叫做可位操作,随着以后的学习深入,你会发现有很多寄存器不支持位操作,这就要想办法了。二进制怎么转换成16进制?16进制怎么转换成二进制呢?答案很简单,用计算器如下图WINDOWS自带的非常好用,还有就是自己算,也比较简单,0XFF 换成二级制也就是1111_1111,
16进制最大计数到F,从0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,共计16个数,比如一个0XFE,是一个8位数,1111是高4位 ,1110看成低四位,从左到右,依次看过去的1对应的是8421,低四位也一样,也是8421,8*1+4*1+2*1+1*1 = 15,也就是16进制的F,8*1+4*1+2*1+1*0 = 14,也就是16进制的E,就是这样算,当然我觉得还是计算器最好用。
  
   关于delay延时函数
还记得我刚开始学的时候,就是不知道这个DELAY函数是怎么来的,在那里苦思冥想,也得不到结果。如果你是一个刚入门的新手,那么这个DELAY函数也不要去研究了,直接用STC的客户端配置好就完事了,注意选择好对应的内核就好了。这样的函数,没有参考的价值,没有研究意义。要定时精准,还是要用定时器来做。


好了,就到这里吧,未完待续。。。
   

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| dabing89 发表于 2018-9-17 18:33 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-13 08:38 编辑

接上一篇帖子,继续来看,既然我们会点亮一个小灯,并可以让其闪烁了,那么我们就尝试着点亮流水灯好了,让8个小灯,从左到右,从右往左来回流动,还是在上一个程序的基础上来修改,很简单依次赋值即可实现。


  1. while(1)
  2.         {
  3.              P2 = 0XFE;//1111_1110;
  4.                  Delay500ms();//500ms延时 11.0592MHZ
  5.                  P2 = 0XFD;//1111_1101;
  6.                  Delay500ms();//500ms延时 11.0592MHZ
  7.                  P2 = 0XFB;//1111_1011;
  8.                  Delay500ms();//500ms延时 11.0592MHZ
  9.                  P2 = 0XF7;//1111_0111;
  10.                  Delay500ms();//500ms延时 11.0592MHZ
  11.                  P2 = 0XEF;//1110_1110;
  12.                  Delay500ms();//500ms延时 11.0592MHZ
  13.                  P2 = 0XDF;//1101_1111;
  14.                  Delay500ms();//500ms延时 11.0592MHZ
  15.                  P2 = 0XBF;   //1011_1110;
  16.                  Delay500ms();//500ms延时 11.0592MHZ
  17.                  P2 = 0X7F;   //0111_1111;
  18.                  Delay500ms();//500ms延时 11.0592MHZ

  19.                  P2 = 0XBF;     //1011_1110;
  20.                  Delay500ms();  //500ms延时 11.0592MHZ
  21.                  P2 = 0XDF;     //1101_1111;
  22.                  Delay500ms();  //500ms延时 11.0592MHZ
  23.                  P2 = 0XEF;     //1110_1110;
  24.                  Delay500ms();  //500ms延时 11.0592MHZ
  25.                  P2 = 0XF7;     //1111_0111;
  26.                  Delay500ms();  //500ms延时 11.0592MHZ
  27.                  P2 = 0XFB;     //1111_0111;
  28.                  Delay500ms();  //500ms延时 11.0592MHZ
  29.                  P2 = 0XFD;     //1111_1101;
  30.                  Delay500ms();  //500ms延时 11.0592MHZ

  31.         }


这样弄,虽然很好理解,但是代码显得太冗长了,我们接触下for循环语句,先用for语句来实现我们想要的功能。代码修改如下:
  1.   for(i = 0; i < 8;i++)
  2.                 {
  3.                         P2 = ~(0X01 << i);
  4.                         Delay500ms();//500ms延时 11.0592MHZ
  5.                 }

  6.                 for(i = 0; i < 6;i++)
  7.                 {
  8.                         P2 = ~(0X40 >> i);
  9.                         Delay500ms();//500ms延时 11.0592MHZ
  10.                 }
用了for循环左移和右移的办法,配合取反实现的,还有一种办法,更简单可以实现来回循环,这便是循环左移和循环右移指令
  1. /*******************************************************************
  2. * 文件名 main函数入口
  3. * 描述:        
  4. * 功 能
  5. * 作者:大核桃
  6. * 版本号:V1.00(2018.09.17)
  7. ********************************************************************/
  8. void main(void)
  9. {
  10.     uint8 i = 0;
  11.         uint8 temp;

  12.         MCU_Port_Init();//端口模式初始化函数
  13.         //上电IO默认是0
  14.         LED0 = 1;//输出1
  15.         LED1 = 0;
  16.         LED2 = 0;
  17.         LED3 = 0;
  18.         LED4 = 0;
  19.         LED5 = 0;
  20.         LED6 = 0;
  21.         LED7 = 0;//

  22.         temp = 0XFE;

  23.         while(1)
  24.         {

  25.            for(i = 0; i < 7;i++)
  26.                 {
  27.                     P2 = temp;
  28.                         temp = _crol_(temp,1);         
  29.                         Delay500ms();//500ms延时 11.0592MHZ
  30.                 }

  31.                 for(i = 0; i < 7;i++)
  32.                 {                              
  33.                     P2 = temp;
  34.                         temp = _cror_(temp,1);
  35.                         Delay500ms();//500ms延时 11.0592MHZ
  36.                 }
  37. }


这一节,用到了for循环这个知识点,for语句的用法在C语言课本的第120页,详细的了解可以看C语言这本书,我们只列举一个实际例子来说明C语言的流程,就拿下面的代码来说吧。
  1.   for(i = 0; i < 8;i++)
  2.                 {
  3.                         P2 = ~(0X01 << i);
  4.                         Delay500ms();//500ms延时 11.0592MHZ
  5.                 }
首先i = 0;是给变量赋初值,i<8是循环条件,i++是变量累加,先来看看i = 0;的时候,这个时候判断下语句P2 = 0XFE,然后0 < 8条件成立,i++;
  1. //  i = 0;的时候
  2. for(i = 0; i < 8;i++)
  3. {
  4.         P2 = ~(0X01 << 0);
  5.         Delay500ms();//500ms延时 11.0592MHZ
  6. }
  1. //  i = 1;的时候
  2. for(i = 0; i < 8;i++)
  3. {
  4.         P2 = ~(0X01 << 1);
  5.         Delay500ms();//500ms延时 11.0592MHZ
  6. }

当 i= 1;的时候,P2 = 0XFD,依然1<8成立,然后i++;
当 i= 2;的时候,P2 = 0XFB,依然2<8成立,然后i++;
当 i= 3;的时候,P2 = 0XF7,依然3<8成立,然后i++;
当 i= 4;的时候,P2 = 0XEF,依然4<8成立,然后i++;
当 i= 5;的时候,P2 = 0XDF,依然5<8成立,然后i++;
当 i= 6;的时候,P2 = 0XBF,依然6<8成立,然后i++;

当 i= 7;的时候,P2 = 0X7F,依然7<8成立,然后i++;
当 i= 8;的时候,8<8不成立,跳出循环执行下面的语句;

这样我们就把for循环的流程给分解了,首先就是赋初值,然后执行循环,循环完之后变量累加,回过头看条件是否成立,如果条件继续成立,则继续循环,累加变量,直到条件不满足为止。
     关于取反符号
~这个符号,可以读作取反,注意这不是!(非),数字电路中的 &(按位与) , |  (按位或),!(非)和 &&(逻辑与),||(逻辑或),和~(取反)是不一样的,按位与顾名思义,是位和位之间运算,他们运算是能够得出一个具体的结果的,而逻辑与是对左右两个表达式的真假进行判断,得出的结果不是真就是假。
    关于与或非的真值表
与或非是数字电路最基本的知识点,必须要掌握这个知识点。
    &, 就好比乘法一样,如果A和B都为1,那么结果就是1,如果有一个为0,则输出都是0,。
    |,   就好比加法运算,如果A或者B有一个是1,那么结果就是1,否则就是0;

    !,     输入为0时,输出为1,输入为1时,输出为0;
   !|,或非门,只有A和B输入全部位0时,输出才为1,否则全为0;
   !&,与非门,只有A和B全部输入为1时,输出才为0,否则全为1;
   异或门,如果A和B相等时,输出都是0,否则输出都是1。


好了,今天就到这里吧,明天继续。。。代码奉献上。。。




   


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| dabing89 发表于 2018-9-19 15:03 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-13 08:47 编辑

                                                                                                             定时器的使用 ---20180919



好,继续更新这个帖子,前边我们点亮了流水灯,然而定时是用DELAY实现的,实际上在实际的项目中,DELAY基本上不会用,如果一个程序中,主循环有大量的DELAY出现,那么就不合理了,所以必须要用定时器 ,保证程序的实时性,这一贴,我们写一个程序,用定时器0来实现LED间隔500MS闪烁的效果   ,代码如下:   
  1. /*******************************************************************
  2. * 文件名  LED0 500MS闪烁
  3. * 描述:        点亮LED---用定时器的办法
  4. * 2018-09-19 调试通过
  5. * 功能  入门模板
  6. * 作者: 大核桃
  7. * 版本号:V1.00(2018.09.19)
  8. ********************************************************************/
  9. #include "config.h"
  10. #include "intrins.h"


  11. /*******************************************************************
  12. * 文件名 变量重新定义区域
  13. * 描述:        
  14. * 功 能
  15. * 作者:大核桃
  16. * 版本号:V1.00(2018.09.17)
  17. ********************************************************************/

  18. typedef unsigned char uint8;//无符号字符型
  19. typedef unsigned int  uint16;//无符号整型
  20. typedef unsigned long uint32;//无符号长整型


  21. /*******************************************************************
  22. * 文件名:位重新定义区域 函数前置声明
  23. * 描述:        
  24. * 功 能
  25. * 作者:大核桃
  26. * 版本号:V1.00(2018.09.17)
  27. ********************************************************************/
  28. void Delay500ms();                //@11.0592MHz
  29. void MCU_Port_Init(void);
  30. void Bsp_Tim0_Init(void);

  31. sbit LED0 = P1^0;
  32. sbit LED1 = P1^1;
  33. sbit LED2 = P1^2;
  34. sbit LED3 = P1^3;
  35. sbit LED4 = P1^4;
  36. sbit LED5 = P3^2;
  37. sbit LED6 = P0^0;
  38. sbit LED7 = P0^1;

  39. bit flag500ms = 0;//500ms定时标志位


  40. /*******************************************************************
  41. * 文件名 main函数入口
  42. * 描述:        
  43. * 功 能
  44. * 作者:大核桃
  45. * 版本号:V1.00(2018.09.17)
  46. ********************************************************************/
  47. void main(void)
  48. {
  49.         MCU_Port_Init();//端口模式初始化函数
  50.         Bsp_Tim0_Init();//定时器0初始化函数


  51.         while(1)
  52.         {           
  53.                 if(flag500ms)
  54.                 {
  55.                         P2 = 0XFE;//1111_1110;
  56.                 }
  57.                 else
  58.                 {
  59.                    P2 = 0XFF;//1111_1111;        
  60.                 }

  61.         }
  62. }

  63. /*******************************************************************
  64. * 文件名:void MCU_Port_Init(void)
  65. * 描述:        MCU端口上电初始化函数
  66. * 功 能
  67. * 作者:大核桃
  68. * 版本号:V1.00(2018.09.17)
  69. ********************************************************************/
  70. void MCU_Port_Init(void)
  71. {
  72.         //第0 和1位配置推完输出模式,大电流
  73.         P0M1 = 0XFC; //        1111_1100
  74.         P0M0 = 0X03; // 0000_0011

  75.         //第01234位配置推完输出模式,大电流,567配置高阻输入,用于ADC
  76.         P1M1 = 0XE0; //1110_0000        
  77.         P1M0 = 0X1F; //0001_1111

  78.         //P2配置位准双向口
  79.         P2M1 = 0X00; //0000_0000        
  80.         P2M0 = 0X00; //0000_0000

  81.         P2 = 0XFF;//P2口初始化为1

  82.         //P5配置位准双向口
  83.         P5M1 = 0X00; //0000_0000        
  84.         P5M0 = 0X00; //0000_0000

  85.         P5 = 0XFF;//P5口初始化为1

  86.         //P3 23467推完输出
  87.         P3M1 = 0X00; //0000_0000        
  88.         P3M0 = 0XFC; //1101_1100

  89.         P3 = 0X23;  //0010_0011


  90.         //上电IO默认是0
  91.         LED0 = 1;//输出1
  92.         LED1 = 0;
  93.         LED2 = 0;
  94.         LED3 = 0;
  95.         LED4 = 0;
  96.         LED5 = 0;
  97.         LED6 = 0;
  98.         LED7 = 0;//
  99.                
  100. }

  101. /*******************************************************************
  102. * 文件名:void Bsp_Tim0_Init(void)
  103. * 描述:        定时器0初始化函数
  104. * 功 能
  105. * 作者:大核桃
  106. * 版本号:V1.00(2018.09.19)
  107. ********************************************************************/
  108. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  109. {
  110.         AUXR |= 0x80;                //定时器时钟1T模式
  111.         TMOD &= 0xF0;                //设置定时器模式
  112.         TMOD |= 0X01;      
  113.         TH0 = 0xD4;                    //设置定时初值
  114.         TL0 = 0xCD;                    //设置定时初值
  115.         TR0 = 1;                    //定时器0开始计时
  116.         ET0 = 1;            //使能定时器0的中断
  117.         EA = 1;             //打开总中断
  118. }

  119. /*******************************************************************
  120. * 文件名:TIM0_IRQ_Handler
  121. * 描  述:中断服务函数        
  122. * 功  能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2
  123. * 优先级:                      0            1    2         3          4           5   6    12
  124. * 版本号:V1.00(2018.09.19)
  125. ********************************************************************/
  126. void TIM0_IRQ_Handler(void) interrupt 1
  127. {
  128.     static uint16 tmr500ms = 0;

  129.         TH0 = 0xD4;                    //设置定时初值
  130.         TL0 = 0xCD;                    //设置定时初值

  131.         tmr500ms++;
  132.         if(tmr500ms >= 500)
  133.         {
  134.                 tmr500ms = 0;

  135.                 flag500ms = !flag500ms;        //500MS闪烁
  136.         }
  137.         
  138.                
  139. }

因为在实际使用中,定时器和中断都是在一起配合使用,所以这儿我们就不分开,但是要说的是,定时器是硬件,是单片机内部存在的一个模块,而中断仅仅是一种处理问题的机制,上面这个d代码看着不多,但是消息量很大,我们一点一点解剖,理解了定时器的的原理,等你上手STM32的时候,定时器原理可以直接不看,直接拿来用就好了。
    先从STC89C52RC的开始说起,我们知道,STC89C52RC是标准51内核,在标准51的体系下,12个时钟周期是一个机器周期,啥意思呢?比如你的外部晶振是11.0592MHZ,那么11059200的倒数,也就是周期了,这个倒数叫做时钟周期,也叫震荡周期,算一下时间,1/11059200 = 0.0904US,这就是STC89C52的时钟周期,那么51单片机就规定,12个这样的时钟周期为一个机器周期,所以在乘以12,那么一个机器周期的数值是1.085US,注意这是在11.0592MHZ下,如果是在12MHZ下,那么一个机器周期就是1US,这就是定时器的时间基准。我们再来看下,如果我们用STC89C52来做一个500MS的定时器该怎么做呢?配置如下即可实现:
  1. /*******************************************************************
  2. * 文件名:void Bsp_Tim0_Init(void)
  3. * 描述:        定时器0初始化函数
  4. * 功 能
  5. * 作者:大核桃
  6. * 版本号:V1.00(2018.09.19)
  7. ********************************************************************/
  8. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  9. {
  10.         TMOD &= 0xF0;                //设置定时器模式
  11.         TMOD |= 0X01;      
  12.         TH0 = 0xFC;                    //设置定时初值
  13.         TL0 = 0x66;                    //设置定时初值
  14.         TR0 = 1;                    //定时器0开始计时
  15.         ET0 = 1;            //使能定时器0的中断
  16.         EA = 1;             //打开总中断

  17. }

  18. /*******************************************************************
  19. * 文件名:TIM0_IRQ_Handler
  20. * 描  述:中断服务函数        
  21. * 功  能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2
  22. * 优先级:                      0            1    2         3          4           5   6    12
  23. * 版本号:V1.00(2018.09.19)
  24. ********************************************************************/
  25. void TIM0_IRQ_Handler(void) interrupt 1
  26. {
  27.     static uint16 tmr500ms = 0;

  28.         TH0 = 0xFC;                    //设置定时初值
  29.         TL0 = 0x66;                    //设置定时初值

  30.         tmr500ms++;
  31.         if(tmr500ms >= 500)
  32.         {
  33.                 tmr500ms = 0;

  34.                 flag500ms = !flag500ms;        //500MS闪烁
  35.         }
  36.         
  37.                
  38. }

还是来解释下,首先定时器的配置步骤是这样的:
1.先设置TMOD这个寄存器,选择定时器0的模式寄存器,配置定时器0为16位不可重装载模式

2.设置定时器的定时初值,高八位和低八位
3.打开定时器的运行标志位,因为TCON是一个可位寻址的寄存器,所以直接TR0 = 1;就好。
4.使能定时器0的中断ET0
5.打开总中断EA
OK,这样就配置好了寄存器,定时器也可以工作了,然而我们了解定时器是怎么运行的了吗?没有!!!很多人不知道为啥是这个数值,而且定时器的的初值还有好几种写法,如果有人用了不一样的而写法,你一定要知道是等价的写法。


  关于初值的计算
   我们知道定时器0是一个16位的定时器,最大计数65536(0-65535)分为高八位和低八位,TH0存储的是高八位的数据,TL0存储的是低八位的数据,0XFC是一个16进制数值,换算10进制是252,0X66是102,我们知道低八位最大计数到255,TH0就变成1,然后进位,清零,又开始从0计数,那么我们可以算算这个初值是多少?252*256 +102 = 64614,而64614的16进制表示形式就是0XFC66,这样我们就搞清楚定时器的计时原理了,如下所示,初值代码可以改写成这样:
  1. TH0 = (65536 - 64614) / 256;                    //设置定时初值
  2.         TL0 = (65536 - 64614) % 256;                    //设置定时初值
也就是说,我们让单片机从64614开始计数,到65535溢出,总共计数921个,而我们又知道1个机器周期是1.085US,那么921个机器周期是多少呢?921*1.085 = 1000US,正好是1MS的定时,我们在程序中让其溢出500次,那么不就是500MS了吗?就是这样来的,原理一定要搞清楚。不管什么STM32,64,128都是这样的原理。关于中断使用的时候,打开使能就好了,EA是总中断使能位,如果这个不打开,ET0单独打开是没用的,这才是一把手。
   关于STC15W系列的定时器
好了,既然我们搞清楚了,STC89C52的定时器的原理了,我们来看下,STC15W的定时器配置,因为我们都是定时1MS,那么,为啥初值不一样呢?我们来算下STC15W的这个初值对应的10进制数值是多少?是54477,好陌生的数字,怎么来的呢?65536-54477 = 11059,也就是说在STC15W的内核下,我们只要计数11059个,就可以达到1MS的定时,我们知道,STC15W是单周期的时钟,也就是说我们不12分频,我们直接就是一个时钟周期就是一个机器周期,(1/11059200)*11059 = 1ms,明白了吧?所以,本节的程序代码,也可以这样写,是一样的作用的。
  1. /*******************************************************************
  2. * 文件名:void Bsp_Tim0_Init(void)
  3. * 描述:        定时器0初始化函数
  4. * 功 能
  5. * 作者:大核桃
  6. * 版本号:V1.00(2018.09.19)
  7. ********************************************************************/
  8. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  9. {
  10.         AUXR |= 0x80;                //定时器时钟1T模式
  11.         TMOD &= 0xF0;                //设置定时器模式
  12.         TMOD |= 0X01;      
  13. //        TH0 = 0xD4;                    //设置定时初值
  14. //        TL0 = 0xCD;                    //设置定时初值
  15.         TH0 = (65536 - 54477) / 256;//设置定时初值
  16.         TL0 = (65536 - 54477) % 256;//设置定时初值
  17.         TR0 = 1;                    //定时器0开始计时
  18.         ET0 = 1;            //使能定时器0的中断
  19.         EA = 1;             //打开总中断

  20. }


关于在单片机中大量存在的& |运算的详细说明
    其实这个,不太想说,但无奈上网看到好多初学者根本不知道& |的作用,还是详细的说明下比较好,以定时器0的配置为例。
    AUXR |= 0x80; AUXR是一个辅助寄存器,这不需要多说,|= 0x80有什么讲究呢?|(或),是让某一位置1的意思,让那一位置一呢?很明显,让是1的那一位置一,0X80不就是最高位是1吗?那就是让最高位置一好了,有人说,这有啥用呢?本来不就是1吗?人家还有后半句,而其他位保持不变,其他位?啥位?等于0的那些对吧?也就是低7位不变了。这样操作有啥好处呢?我们知道ET0 = 1;TR0 = 1;之所以可以这样操作,是因为他们可以被位寻址,可以进行单独的位操作,而AUXR是不可以进行位寻址的,因此一次操作必须操作8个位,你想想看,AUXR这个寄存器的功能如下图:

   你能保证直接让AUXR = 0X80,不对其他位造成影响吗?现在大家的编程还比较简单,只有一个定时器,要是用到3个,4个定时器呢?这样不就互相干扰了吗?扯淡么?所以 & |的重要性也就凸现出来了。
   再来看后面这2句,很明显TMOD是不可以位寻址的,按照我们刚才的分析,|是让某一位置一,那么&,自然就是让某一位清零了,来看下0XF0,二进制是1111_0000,也就是低4位置0,高4位不要管,因为&是乘法运算啊,只要都是1,那么就是1,很明显让低4位清零,下一句是0000_0001,让最低为置一啊,对吧,要注意,这里的2句是连续操作的,不是单独的的操作,什么意思?前者的运算结果,又给了后者,所以我们总体来看这2句代码,先让低四位清零,高四位不变,然后将这个结果进行或运算,让最低位置一,而高7位都不变,因为任何数|还是任何数啊,对吧,这就达到了一个互不干扰的目的,这样的代码在STM32上好多好多的,都是起到一个互不干扰的作用。这样的做法可以确保定时器0和定时器1是独立的,如果我们不这样做,你看看是工作在啥模式?除了定时器配置在16位不可重装模式在,定时器1被配置在了16位自动重装定时器,我们没有使用定时器1,万一出错怎么办?这就不好了。
  

关于自动重装载和不可自动重装模式
  其实,没啥太大的区别,如果是自动重装,那么在中断服务函数中,TH0 TL0就不需要去再去重新赋值了,直接删掉就好,如果不是自动重装,则必须要加。还差点忘了一个事,你怎么确定你的定时是500MS呢?答案不能靠眼睛看把,看看示波器观察的结果,嗯,是对的。如下图,1S 1HZ的方波信号

好了,今天就到这里吧,代码上传




  1. TMOD &= 0xF0;                //设置定时器模式
  2.         TMOD |= 0X01;       //设置定时器模式



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

评论

支持  发表于 2018-9-19 22:46
owencai 发表于 2018-9-21 11:26 | 显示全部楼层
学习学习!
 楼主| dabing89 发表于 2018-9-21 15:31 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-13 08:57 编辑

                                                                    STC15W1K16PWM内部EEPROM的使用--20180921

     在实际开发中,经常会遇到某些需要断电需要再次上电**的场合,这就需要掉电存储芯片了,最常用的EEPROM芯片就是AT24C02了,几乎成了每一块开发板的标配,但是有些时候,在一些低成本的场合,需要用类EEPROM或者flash来模拟EEPROM进行存储,AT24C02是可以进行字节擦写的,STC内部的EEPROM是不可以字节擦写的,他是按照512byte一个扇区来组织的,如下图所示,我们开发板选择的这块芯片分为了22个扇区。如果你要擦除数据,那么必须要一次性的擦除512字节才可以,这么难用,难用总比没有强吧,在好多产品上,我都见到过,好多掉电了上电依然保持的参数,有没有外置EEPROM芯片,只能用内部的或者来模拟了,我们来写一个程序,程序的结果是**上电次数,每上电一次,就累加一次,显示在数码管上面,OK,写好的代码如下所示:


  1. /*******************************************************************************
  2. * 文件名: 数码管显示上电计数值
  3. * 描  述: 上电计数
  4. * 功  能:数码管的使用
  5. * 作  者:大核桃 597627977
  6. * 版本号:1.0.1(2018.09.21)
  7. *******************************************************************************/
  8. #include "stc15w.h"//头文件
  9. #include "intrins.h"

  10. /*******************************************************************************
  11. * 文件名: 重定义
  12. * 描  述:   
  13. * 功  能:
  14. * 作  者:大核桃
  15. * 版本号:1.0.1(2018.09.21)
  16. *******************************************************************************/
  17. typedef unsigned char uint8;
  18. typedef unsigned int  uint16;
  19. typedef unsigned long uint32;

  20. /*******************************************************************************
  21. * 文件名:共阳数码管真值表
  22. * 描  述:
  23. * 功  能:
  24. * 作  者:大核桃
  25. * 版本号:1.0.1(2018.09.21)
  26. *******************************************************************************/
  27. code uint8 LedChar[] = {
  28.         0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
  29.         0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
  30. };

  31. uint16 counter;        //**上电次数,最大65535

  32. /*******************************************************************************
  33. * 文件名:单独位定义
  34. * 描  述:   
  35. * 功  能:
  36. * 作  者:大核桃
  37. * 版本号:1.0.1(2018.09.21)
  38. *******************************************************************************/
  39. sbit LED0 = P1^0;//第1组LED
  40. sbit LED1 = P1^1;//第2组LED
  41. sbit LED2 = P1^2;//第3组LED                                 
  42. sbit LED3 = P1^3;//第4组LED
  43. sbit LED4 = P1^4;//第5组LED
  44. sbit LED5 = P3^2;//第6组LED
  45. sbit LED6 = P0^0;//第7组LED
  46. sbit LED7 = P0^1;//第8组LED

  47. sbit LEDS1 = P3^3;//数码管1
  48. sbit LEDS2 = P3^4;//数码管2
  49. sbit LEDS3 = P3^6;//数码管3
  50. sbit LEDS4 = P3^7;//数码管4

  51. /*******************************************************************************
  52. * 文件名:全局变量定义区域
  53. * 描  述:
  54. * 功  能:
  55. * 作  者:大核桃
  56. * 版本号:1.0.1(2017.05.23)
  57. *******************************************************************************/
  58. #define CMD_IDLE    0               //空闲模式
  59. #define CMD_READ    1               //IAP字节读命令
  60. #define CMD_PROGRAM 2               //IAP字节编程命令
  61. #define CMD_ERASE   3               //IAP扇区擦除命令

  62. #define ENABLE_IAP  0x82            //if SYSCLK<20MHz
  63. /*******************************************************************************
  64. * 文件名:函数前置声明
  65. * 描  述:   
  66. * 功  能:
  67. * 作  者:大核桃
  68. * 版本号:1.0.1(2017.05.23)
  69. *******************************************************************************/
  70. void Mcu_Port_Init();
  71. void LedScan();
  72. void Delay500ms(); //24MHZ
  73. void Time0_Init();//定时器0
  74. void IapIdle();
  75. uint8 IapReadByte(uint16 addr);
  76. void IapProgramByte(uint16 addr, uint8 dat);
  77. void IapEraseSector(uint16 addr);
  78. #define Delay()                {_nop_();_nop_();_nop_();_nop_();}

  79. /*******************************************************************************
  80. * 文件名
  81. * 描  述: 主函数  
  82. * 功  能:入口
  83. * 作  者:大核桃
  84. * 版本号:1.0.1(2017.05.23)
  85. *******************************************************************************/
  86. void main(void)
  87. {                                       
  88.         counter = IapReadByte(0x0000);//读取数据
  89.         counter++;//写

  90.         Mcu_Port_Init();//IO上电初始化
  91.         Time0_Init();

  92.         IapEraseSector(0x0000);//擦除数据
  93.         IapProgramByte(0x0000, counter);//写入数据


  94.         while(1);
  95. }

  96. /*******************************************************************************
  97. * 文件名:void LedScan()
  98. * 描  述: LED刷新
  99. * 功  能:
  100. * 作  者:大核桃
  101. * 版本号:1.0.1(2017.05.23)
  102. *******************************************************************************/
  103. void LedScan()
  104. {
  105.         static uint8 i = 0;

  106.         P2 = 0Xff;
  107.         switch(i)
  108.         {
  109.                 case 0: LEDS4 = 0;LEDS1 = 1;P2 = LedChar[counter / 1000 % 10];i++;break;
  110.                 case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[counter / 100 % 10];i++;break;
  111.                 case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[counter / 10 % 10];i++;break;
  112.                 case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[counter % 10];i = 0;break;

  113.                 default:break;
  114.         }
  115. }
  116. /*******************************************************************************
  117. * 文件名:void Time0_Init()
  118. * 描  述: 定时器0初始化
  119. * 功  能:10毫秒@11.0592MHz
  120. * 作  者:大核桃
  121. * 版本号:1.0.1(2017.05.23)
  122. *******************************************************************************/
  123. void Time0_Init(void)
  124. {
  125.         AUXR &= 0x7F;                //定时器时钟12T模式
  126.         TMOD &= 0xF0;                //设置定时器模式
  127.         TMOD |= 0X01;      //确保不干扰其他配置
  128.         TH0 = 0xDC;                //设置定时初值
  129.         TL0 = 0x00;                //设置定时初值
  130.         ET0 = 1;
  131.         TR0 = 1;                //定时器0开始计时
  132.         EA = 1;               
  133. }
  134. /*******************************************************************************
  135. * 文件名:
  136. * 描  述: 中断函数
  137. * 功  能:10毫秒@11.0592MHz
  138. * 作  者:大核桃
  139. * 版本号:1.0.1(2017.05.23)
  140. *******************************************************************************/
  141. void ET0_IRQHandler() interrupt 1
  142. {
  143.         TH0 = 0xDC;                //设置定时初值
  144.         TL0 = 0x00;                //设置定时初值
  145.         LedScan();               
  146. }
  147. /*******************************************************************************
  148. * 文件名:void Mcu_Port_Init()
  149. * 描  述: io初始化
  150. * 功  能:
  151. * 作  者:大核桃
  152. * 版本号:1.0.1(2017.05.23)
  153. *******************************************************************************/
  154. void Mcu_Port_Init()
  155. {
  156.         //将P0口低二位配置为推挽输出
  157.         //234567位配置位高阻输入
  158.         P0M1 = 0xFC;//1111 1100
  159.         P0M0 = 0X03;//0000 0011
  160.         //P0 = 0X01;//第6个
  161.         //P0 = 0X02;//第7个
  162.         //高3位配置高阻输入,用作模拟口
  163.         //其他配置推挽输出,驱动LED
  164.         P1M1 = 0xE0;//1110 0000
  165.         P1M0 = 0X1F;//0001 1111
  166.         //P2口配置准双向口
  167.         P2M1 = 0X00;
  168.         P2M0 = 0X00;
  169.         P2 = 0Xff; //上电为1111 1111

  170. //        //P54,P55口为推挽输出
  171.         P5M1 = 0X00;
  172.         P5M0 = 0X00;
  173.         P5 = 0xFF;

  174.         //P37,P36,3.2,P3.3 P3.4口为推挽输出
  175.         P3M1 = 0X00;
  176.         P3M0 = 0XFC;
  177.         P3 = 0X23; //0010 0111//第5个LED端口
  178.         
  179.         LED0 = 0;//第1组LED,如果使能请置为1
  180.         LED1 = 0;
  181.         LED2 = 0;
  182.         LED3 = 0;
  183.         LED4 = 0;
  184.         LED5 = 0;
  185.         LED6 = 0;
  186.         LED7 = 0;        
  187. }

  188. /*******************************************************************************
  189. * 文件名:void Delay500ms()                //@24.000MHz
  190. * 描  述:Y5内核延时
  191. * 功  能:
  192. * 作  者:大核桃
  193. * 版本号:1.0.1(2017.05.23)
  194. *******************************************************************************/
  195. void Delay500ms()                //@24.000MHz
  196. {
  197.         unsigned char i, j, k;

  198.         _nop_();
  199.         _nop_();
  200.         i = 46;
  201.         j = 153;
  202.         k = 245;
  203.         do
  204.         {
  205.                 do
  206.                 {
  207.                         while (--k);
  208.                 } while (--j);
  209.         } while (--i);
  210. }
  211. /*******************************************************************************
  212. * 文件名:void IapIdle()
  213. * 描  述:关闭IAP
  214. * 功  能:
  215. * 作  者:大核桃
  216. * 版本号:1.0.1(2017.05.23)
  217. *******************************************************************************/
  218. void IapIdle()
  219. {
  220.     IAP_CONTR = 0;                  //关闭IAP功能
  221.     IAP_CMD = 0;                    //清除命令寄存器
  222.     IAP_TRIG = 0;                   //清除触发寄存器
  223.     IAP_ADDRH = 0x80;               //将地址设置到非IAP区域
  224.     IAP_ADDRL = 0;
  225. }
  226. /*******************************************************************************
  227. * 文件名:uint8 IapReadByte(uint16 addr)
  228. * 描  述:从ISP/IAP/EEPROM区域读取一字节
  229. * 功  能:
  230. * 作  者:大核桃
  231. * 版本号:1.0.1(2017.05.23)
  232. *******************************************************************************/
  233. uint8 IapReadByte(uint16 addr)
  234. {
  235.     uint8 dat;                       //数据缓冲区

  236.     IAP_CONTR = ENABLE_IAP;         //使能IAP
  237.     IAP_CMD = CMD_READ;             //设置IAP命令
  238.     IAP_ADDRL = addr;               //设置IAP低地址
  239.     IAP_ADDRH = addr >> 8;          //设置IAP高地址
  240.     IAP_TRIG = 0x5a;                //写触发命令(0x5a)
  241.     IAP_TRIG = 0xa5;                //写触发命令(0xa5)
  242.         _nop_();                        //等待ISP/IAP/EEPROM操作完成
  243.     dat = IAP_DATA;                 //读ISP/IAP/EEPROM数据
  244.     IapIdle();                      //关闭IAP功能

  245.     return dat;                     //返回
  246. }
  247. /*******************************************************************************
  248. * 文件名:void IapProgramByte(uint16 addr, uint8 dat)
  249. * 描  述: 写一字节数据到ISP/IAP/EEPROM区域
  250. * 功  能:
  251. * 作  者:大核桃
  252. * 版本号:1.0.1(2017.05.23)
  253. *******************************************************************************/
  254. void IapProgramByte(uint16 addr, uint8 dat)
  255. {
  256.     IAP_CONTR = ENABLE_IAP;         //使能IAP
  257.     IAP_CMD = CMD_PROGRAM;          //设置IAP命令
  258.     IAP_ADDRL = addr;               //设置IAP低地址
  259.     IAP_ADDRH = addr >> 8;          //设置IAP高地址
  260.     IAP_DATA = dat;                 //写ISP/IAP/EEPROM数据
  261.     IAP_TRIG = 0x5a;                //写触发命令(0x5a)
  262.     IAP_TRIG = 0xa5;                //写触发命令(0xa5)
  263.         _nop_();                        //等待ISP/IAP/EEPROM操作完成
  264.     IapIdle();
  265. }

  266. /*******************************************************************************
  267. * 文件名:void IapEraseSector(uint16 addr)
  268. * 描  述: 扇区擦除
  269. * 功  能:
  270. * 作  者:大核桃
  271. * 版本号:1.0.1(2017.05.23)
  272. *******************************************************************************/
  273. void IapEraseSector(uint16 addr)
  274. {
  275.     IAP_CONTR = ENABLE_IAP;         //使能IAP
  276.     IAP_CMD = CMD_ERASE;            //设置IAP命令
  277.     IAP_ADDRL = addr;               //设置IAP低地址
  278.     IAP_ADDRH = addr >> 8;          //设置IAP高地址
  279.     IAP_TRIG = 0x5a;                //写触发命令(0x5a)
  280.     IAP_TRIG = 0xa5;                //写触发命令(0xa5)
  281.     _nop_();                        //等待ISP/IAP/EEPROM操作完成
  282.     IapIdle();
  283. }

程序上电后的执行效果图片如下:可以看到程序记录上电12次,稍后我们详细的解析下这个程序。


关于数码管的一些问题
     一个8段的数码管其实就是8个小灯啊,我们知道LED是有方向的,只有加正向偏置电压才会点亮,正极的一端是阳极,负极的一端是阴极,如果我们把所有的阳极连到一个公共点,通过给其阴极一个低电位的方法能够点亮的,叫做共阳极数码管,那么共阴极数码管就是倒过来了,高电平点亮,所有的阴极连在一起,限流电阻是友情提供的,实际是没有的,如下图所示:

有人可能觉得,那这8个小灯是如何排列的啊?怎么看呢?客官,您别急,我来画一下,您就明白了。如下图所示,共阳极数码管示意图:

有了这张图,我们来看一下程序,就好办了,想一想,如果我要在数码管上显示一个数字0怎么弄呢?如果是共阳极数码管。我应该让ABCDEF都是0才可以,也即是说,点亮该段即可实现,那么结合我们前面所讲解的数字电路知识,最高位我们不管,默认1即可 就是说要显示一个0,那么八段从低到高依次是,a = 0,b = 0,c = 0,d = 0,e = 0,f = 0,g = 1,dot = 1;也就是二进制的1100_0000,16进制是0XC0,如果我们想要0-9这10个数字,那么是不是可以用同样的方式,算出来,好了,真值表就是这么来的,至于共阳极,取反一下就是了。我们新建一个无符号字符型数组,将我们算好的数据放进数组里面。
  1. /*******************************************************************************
  2. * 文件名:共阳数码管真值表
  3. * 描  述:
  4. * 功  能:
  5. * 作  者:大核桃
  6. * 版本号:1.0.1(2018.09.21)
  7. *******************************************************************************/
  8. code uint8 LedChar[] = {
  9.         0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
  10.         0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
  11. };
前面为什么要加一个CODE关键字呢?51单片机有好多关键字,默认都是蓝色标识,注意,这个表示这个关键字在单片机系统中已经有名字了,不能随便命名,CODE关键字的意思是将该部分代码放在FLASH里面,而不是放在RAM里面,节省了程序运行空间,放在FLASH里面的变量是不能在程序运行时改变的。


关于数码管的扫描刷新
我们了解一个常识,就是人的眼睛是不能够分辨刷新速度小于10MS的物体的,就算变化了,你也看不出来的,最好的例子,就是,拿手机拍电视录像,一条条的,就是因为手机拍摄的速度太快,而电视画面刷新的太慢造成的,而这样的现象,我们是看不见的。
用数码管来显示数字,基本上都是动态扫描刷新,所谓动态扫描,也就是先在1数码管赋值,然后切换到2数码管,切换到3,来回切换,我们只要把刷新速度控制在10MS之内,那么人的眼睛也看不出来的,我们这个代码就是这样进行处理的,如下所示;
  1. /*******************************************************************************
  2. * 文件名:void LedScan()
  3. * 描  述: LED刷新
  4. * 功  能:
  5. * 作  者:大核桃
  6. * 版本号:1.0.1(2017.05.23)
  7. *******************************************************************************/
  8. void LedScan()
  9. {
  10.         static uint8 i = 0;

  11.         P2 = 0Xff;
  12.         switch(i)
  13.         {
  14.                 case 0: LEDS4 = 0;LEDS1 = 1;P2 = LedChar[counter / 1000 % 10];i++;break;
  15.                 case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[counter / 100 % 10];i++;break;
  16.                 case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[counter / 10 % 10];i++;break;
  17.                 case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[counter % 10];i = 0;break;

  18.                 default:break;
  19.         }
  20. }

我们用到了SWITCH语句,SWITCH是一条多选一语句,以CASE为分支,break语句作为结束。我们来看下开发的原理图,4个数码管分别是NLED0,NLED1,NLED2,NLED3,这个段码和位码是如何选择的呢?用万用表的二极管档位,我们知道二极管是单向导电的,我们又知道正向偏置是可以点亮小灯的,不断的变换万用表的表笔,将亮的段位和引脚记下来,按照提供的数码管引脚图就可以分出段码和位码来。

如果我们要显示一个1,打开对应的IO,那么我们只要对P2赋值P2 = LedChar[1]就好了;可是在实际应用中,我们需要显示的更加复杂,因此,只能这样动态进行赋值了,新建一个counter变量,然后将最低位的数码管显示个位,第二个数码管显示10位,第三个数码管显示百位,第四个数码管显示千位,依次这样,相除取余数即可实现。


关于内部EEPROM
这个代码,是从STC的客户端上复制下来的,稍微整理了一下,不需要深入学习,你只要知道有多少个扇区,每个扇区的起始地址,就可以了,必要时候,回来翻阅数据手册就可以搞定,使用的时候,一定要注意,同一扇区的数据会全部被擦除掉,如果不想全部擦除,一定要写到不同的扇区,我们实现的功能是,先上电读取一次0X0000地址的数据,然后我们counter++,然后我们擦除0X0000地址的数据,在重新向0X0000地址写入一个新的数据就OK,注意,写入之前先擦除,不然写不进去的。


今天的,就到这里吧,代码献上







本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| dabing89 发表于 2018-9-26 15:26 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-13 09:07 编辑

                                                                                    利用STC单片机的ADC采集电压值----20180926





在实际开发中,AD,DA用的相当多,这一节,我们写一个程序,将采集到的电压数值显示在数码管上,程序如下:
  1. /*******************************************************************************
  2. * 文件名: ADC转换器使用
  3. * 描  述: 电池电压
  4. * 功  能:中断方式 11.059200MHZ
  5. * 作  者:大核桃
  6. * 版本号:1.0.1(2017.05.23)
  7. *******************************************************************************/
  8. #include "stc15w.h"//头文件
  9. #include "intrins.h"


  10. /*******************************************************************************
  11. * 文件名: 重定义
  12. * 描  述:   
  13. * 功  能:
  14. * 作  者:大核桃
  15. * 版本号:1.0.1(2017.05.23)
  16. *******************************************************************************/
  17. typedef unsigned char uint8;
  18. typedef unsigned int  uint16;
  19. typedef unsigned long uint32;

  20. #define ADC_POWER   0x80            //ADC电源控制位
  21. #define ADC_FLAG    0x10            //ADC完成标志
  22. #define ADC_START   0x08            //ADC起始控制位
  23. #define ADC_SPEEDLL 0x00            //540个时钟

  24. /*******************************************************************************
  25. * 文件名:全局变量定义区域
  26. * 描  述:
  27. * 功  能:
  28. * 作  者:大核桃
  29. * 版本号:1.0.1(2015.03.03)
  30. *******************************************************************************/
  31. uint8 val,ch;
  32. uint16 temp;
  33. /*******************************************************************************
  34. * 文件名:共阳数码管真值表
  35. * 描  述:
  36. * 功  能:
  37. * 作  者:大核桃
  38. * 版本号:1.0.1(2015.03.03)
  39. *******************************************************************************/
  40. code uint8 LedChar[] = {
  41.         0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
  42.         0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff,0xc1
  43. };

  44. /*******************************************************************************
  45. * 文件名:单独位定义
  46. * 描  述:   
  47. * 功  能:
  48. * 作  者:大核桃
  49. * 版本号:1.0.1(2017.05.23)
  50. *******************************************************************************/
  51. sbit LED0 = P1^0;//第1组LED
  52. sbit LED1 = P1^1;//第2组LED
  53. sbit LED2 = P1^2;//第3组LED                                 
  54. sbit LED3 = P1^3;//第4组LED
  55. sbit LED4 = P1^4;//第5组LED
  56. sbit LED5 = P3^2;//第6组LED
  57. sbit LED6 = P0^0;//第7组LED
  58. sbit LED7 = P0^1;//第8组LED

  59. sbit LEDS1 = P3^3;//数码管1
  60. sbit LEDS2 = P3^4;//数码管2
  61. sbit LEDS3 = P3^6;//数码管3
  62. sbit LEDS4 = P3^7;//数码管4

  63. /*******************************************************************************
  64. * 文件名:函数前置声明
  65. * 描  述:   
  66. * 功  能:
  67. * 作  者:大核桃
  68. * 版本号:1.0.1(2017.05.23)
  69. *******************************************************************************/
  70. void Mcu_Port_Init();
  71. void LedScan();
  72. void Delay500ms(); //24MHZ
  73. void Time0_Init();//定时器0
  74. void InitADC(void);

  75. /*******************************************************************************
  76. * 文件名
  77. * 描  述: 主函数  
  78. * 功  能:入口
  79. * 作  者:大核桃
  80. * 版本号:1.0.1(2017.05.23)
  81. *******************************************************************************/
  82. void main(void)
  83. {                                       
  84.         Mcu_Port_Init();//IO上电初始化
  85.         Time0_Init();
  86.         InitADC();
  87.         while(1);
  88. }

  89. /*******************************************************************************
  90. * 文件名:void LedScan()
  91. * 描  述: LED刷新
  92. * 功  能:
  93. * 作  者:大核桃
  94. * 版本号:1.0.1(2017.05.23)
  95. *******************************************************************************/
  96. void LedScan()
  97. {
  98.         static uint8 i = 0;

  99.         P2 = 0Xff;
  100.         switch(i)
  101.         {
  102.                 case 0: LEDS4 = 0;LEDS1 = 1;P2 = 0x7f & LedChar[16];i++;break;
  103.                 case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[val / 10 % 10];i++;break;
  104.                 case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[val % 10];i++;break;
  105.                 case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[17];i = 0;break;

  106.                 default:break;
  107.         }
  108. }

  109. /*******************************************************************************
  110. * 文件名:void InitADC(void)
  111. * 描  述: //初始化 AD 转换
  112. * 功  能:
  113. * 作  者:大核桃
  114. * 版本号:1.0.1(2015.03.03)
  115. *******************************************************************************/
  116. void InitADC(void)
  117. {
  118.         P1ASF = 0xE0; //设置 P1 口为模拟口
  119.         ADC_RES = 0; //清除结果寄存器
  120.         CLK_DIV |= 0x20; //ADRJ 为 1,ADC_RES 存放高两位结果,ADC_RESL 存放低 8 位结果
  121.     //ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START;
  122.         ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START;                    //ADC上电并延时
  123. }

  124. /*******************************************************************************
  125. * 文件名:void adc_isr() interrupt 5 using 1
  126. * 描  述: 中断服务程序
  127. * 功  能:
  128. * 作  者:大核桃
  129. * 版本号:1.0.1(2015.03.03)
  130. *******************************************************************************/
  131. void adc_isr() interrupt 5 using 1
  132. {
  133.         EADC = 1; //允许ADC转换中断
  134.         ET0 = 0;  //关闭定时器0中断

  135.         ADC_CONTR &= !ADC_FLAG; //清除ADC中断标志
  136.         temp = ADC_RES;
  137.         temp <<= 8;
  138.         temp |= ADC_RESL;

  139.         ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | 5;
  140.         EADC = 0;//关闭ADC转换
  141.         ET0 = 1;  //打开定时器0中断        

  142. }

  143. /*******************************************************************************
  144. * 文件名:void VoltageCheckRefresh()
  145. * 描  述: 电压检测
  146. * 功  能:
  147. * 作  者:大核桃
  148. * 版本号:1.0.1(2015.03.03)
  149. *******************************************************************************/
  150. void VoltageCheckRefresh()
  151. {
  152.         val = (uint8)((temp)* 2 * 10 * 3.3 / 1023);                        
  153. }

  154. /*******************************************************************************
  155. * 文件名:void Time0_Init()
  156. * 描  述: 定时器0初始化
  157. * 功  能:10毫秒@11.0592MHz
  158. * 作  者:大核桃
  159. * 版本号:1.0.1(2017.05.23)
  160. *******************************************************************************/
  161. void Time0_Init(void)
  162. {
  163.         AUXR &= 0x7F;                //定时器时钟12T模式
  164.         TMOD &= 0xF0;                //设置定时器模式
  165.         TMOD |= 0X01;      //确保不干扰其他配置
  166.         TH0 = (65536 - 1000) / 256;                //设置定时初值
  167.         TL0 = (65536 - 1000) % 256;                //设置定时初值
  168.         ET0 = 1;
  169.         TR0 = 1;                //定时器0开始计时
  170.         EA = 1;               
  171. }
  172. /*******************************************************************************
  173. * 文件名:
  174. * 描  述: 中断函数
  175. * 功  能:5毫秒@11.0592MHz
  176. * 作  者:大核桃
  177. * 版本号:1.0.1(2017.05.23)
  178. *******************************************************************************/
  179. void ET0_IRQHandler() interrupt 1
  180. {
  181.         static uint8 tmr5ms = 0;

  182.         TH0 = (65536 - 1000) / 256;                //设置定时初值
  183.         TL0 = (65536 - 1000) % 256;                //设置定时初值

  184.         EADC = 0; //不允许ADC转换中断
  185.         tmr5ms++;
  186.         if(tmr5ms >= 5)
  187.         {
  188.                 tmr5ms = 0; //定时器5MS溢出一次
  189.                 VoltageCheckRefresh();
  190.         }

  191.         LedScan();
  192.         EADC = 1; //允许ADC转换中断
  193.                         
  194. }
  195. /*******************************************************************************
  196. * 文件名:void Mcu_Port_Init()
  197. * 描  述: io初始化
  198. * 功  能:
  199. * 作  者:大核桃
  200. * 版本号:1.0.1(2017.05.23)
  201. *******************************************************************************/
  202. void Mcu_Port_Init()
  203. {
  204.         IE = 0xa8;//允许AD转换
  205.         //将P0口低二位配置为推挽输出
  206.         //234567位配置位高阻输入
  207.         P0M1 = 0xFC;//1111 1100
  208.         P0M0 = 0X03;//0000 0011
  209.         //P0 = 0X01;//第6个
  210.         //P0 = 0X02;//第7个
  211.         //高3位配置高阻输入,用作模拟口
  212.         //其他配置推挽输出,驱动LED
  213.         P1M1 = 0xE0;//1110 0000
  214.         P1M0 = 0X1F;//0001 1111
  215.         //P2口配置准双向口
  216.         P2M1 = 0X00;
  217.         P2M0 = 0X00;
  218.         P2 = 0Xff; //上电为1111 1111

  219. //        //P54,P55口为推挽输出
  220.         P5M1 = 0X00;
  221.         P5M0 = 0X00;
  222.         P5 = 0xFF;

  223.         //P37,P36,3.2,P3.3 P3.4口为推挽输出
  224.         P3M1 = 0X00;
  225.         P3M0 = 0XFC;
  226.         P3 = 0X23; //0010 0111//第5个LED端口
  227.         
  228.         LED0 = 0;//第1组LED,如果使能请置为1
  229.         LED1 = 0;
  230.         LED2 = 0;
  231.         LED3 = 0;
  232.         LED4 = 0;
  233.         LED5 = 0;
  234.         LED6 = 0;
  235.         LED7 = 0;        
  236. }

  237. /*******************************************************************************
  238. * 文件名:void Delay500ms()                //@11.0592MHz
  239. * 描  述:Y5内核延时
  240. * 功  能:
  241. * 作  者:大核桃
  242. * 版本号:1.0.1(2017.05.23)
  243. *******************************************************************************/
  244. void Delay500ms()                //@11.0592MHz
  245. {
  246.         unsigned char i, j, k;

  247.         _nop_();
  248.         _nop_();
  249.         i = 22;
  250.         j = 3;
  251.         k = 227;
  252.         do
  253.         {
  254.                 do
  255.                 {
  256.                         while (--k);
  257.                 } while (--j);
  258.         } while (--i);
  259. }

在数码管上显示的数值如下图所示,注意这是测试的锂电池的电压:
      

实际万用表测量的结果是4.01V,数码管显示的结果跟万用表的结果相差0.01V,我们用的ADC是10位的分辨率,参考电压是3.3V,那么一个LSB是3.3/1024 = 0.00322265625,那么0.01V,差不多3个LSB,考虑到我们的参考电压也是有少许误差存在的,所以这个结果还是比较合理的,如果使用独立的参考电压,误差应该会更小。




代码奉献上。




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
MEROXMX 发表于 2018-10-8 16:44 | 显示全部楼层
好人一生平安!
 楼主| dabing89 发表于 2018-10-9 18:17 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-13 09:19 编辑

                                                                 更加实用的ADC转换程序---20181009


     哎呀,回家呆了好些天,干了一些活,好累,来接着继续分享单片机的一些学习经历,上一次,我们写了一个测试电压的程序,但是这样的程序是不实用的,所以我们这一节,来介绍个比较实用的程序,还是测电池电压,然后我们解释一下这个代码,这一节狠狠狠重要。。。
     先把代码献上,然后我们再来分析
  1. <font color="#000000">/*******************************************************************************
  2. * 文件名: ADC转换器使用
  3. * 描  述: 电池电压
  4. * 功  能:中断方式
  5. * 作  者:大核桃
  6. * 版本号:1.0.1(2017.05.23)
  7. *******************************************************************************/
  8. #include "stc15w.h"//头文件
  9. #include "intrins.h"



  10. /*******************************************************************************
  11. * 文件名: 重定义
  12. * 描  述:   
  13. * 功  能:
  14. * 作  者:大核桃
  15. * 版本号:1.0.1(2017.05.23)
  16. *******************************************************************************/
  17. typedef unsigned char uint8;
  18. typedef unsigned int  uint16;
  19. typedef unsigned long uint32;

  20. #define ADC_POWER   0x80            //ADC电源控制位
  21. #define ADC_FLAG    0x10            //ADC完成标志
  22. #define ADC_START   0x08            //ADC起始控制位
  23. #define ADC_SPEEDLL 0x00            //540个时钟
  24. //#define ADC_SPEEDL  0x20            //360个时钟
  25. //#define ADC_SPEEDH  0x40            //180个时钟
  26. //#define ADC_SPEEDHH 0x60            //90个时钟

  27. /*******************************************************************************
  28. * 文件名:全局变量定义区域
  29. * 描  述:
  30. * 功  能:
  31. * 作  者:大核桃
  32. * 版本号:1.0.1(2015.03.03)
  33. *******************************************************************************/
  34. uint8 val,ch;
  35. uint16 temp;
  36. bit flag_ad2 = 0;//电压采集完成标志
  37. bit flag_coll1 = 0;//数据采集间隔
  38. uint16 Adresult_val = 0;//采集的AD数值xx
  39. uint8 ad_count = 0; //采集AD的次数计数器

  40. /*******************************************************************************
  41. * 文件名:共阳数码管真值表
  42. * 描  述:
  43. * 功  能:
  44. * 作  者:大核桃
  45. * 版本号:1.0.1(2015.03.03)
  46. *******************************************************************************/
  47. code uint8 LedChar[] = {
  48.         0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
  49.         0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff,0xc1
  50. };

  51. /*******************************************************************************
  52. * 文件名:单独位定义
  53. * 描  述:   
  54. * 功  能:
  55. * 作  者:大核桃
  56. * 版本号:1.0.1(2017.05.23)
  57. *******************************************************************************/
  58. sbit LED0 = P1^0;//第1组LED
  59. sbit LED1 = P1^1;//第2组LED
  60. sbit LED2 = P1^2;//第3组LED                                 
  61. sbit LED3 = P1^3;//第4组LED
  62. sbit LED4 = P1^4;//第5组LED
  63. sbit LED5 = P3^2;//第6组LED
  64. sbit LED6 = P0^0;//第7组LED
  65. sbit LED7 = P0^1;//第8组LED

  66. sbit LEDS1 = P3^3;//数码管1
  67. sbit LEDS2 = P3^4;//数码管2
  68. sbit LEDS3 = P3^6;//数码管3
  69. sbit LEDS4 = P3^7;//数码管4

  70. /*******************************************************************************
  71. * 文件名:函数前置声明
  72. * 描  述:   
  73. * 功  能:
  74. * 作  者:大核桃
  75. * 版本号:1.0.1(2017.05.23)
  76. *******************************************************************************/
  77. void Mcu_Port_Init();
  78. void LedScan();
  79. void Delay500ms(); //24MHZ
  80. void Time0_Init();//定时器0
  81. void InitADC(void);
  82. uint16 VolTage_Monitor(uint8 times); //AD转换与查表处理程序

  83. /*******************************************************************************
  84. * 文件名
  85. * 描  述: 主函数  
  86. * 功  能:入口
  87. * 作  者:大核桃
  88. * 版本号:1.0.1(2017.05.23)
  89. *******************************************************************************/
  90. void main(void)
  91. {                                       
  92.         Mcu_Port_Init();//IO上电初始化
  93.         Time0_Init();
  94.         InitADC();

  95.         while(1)
  96.         {
  97.                 VolTage_Monitor(16);//采集16次数据
  98.         }
  99. }

  100. /*******************************************************************************
  101. * 文件名:void LedScan()
  102. * 描  述: LED刷新
  103. * 功  能:
  104. * 作  者:大核桃
  105. * 版本号:1.0.1(2017.05.23)
  106. *******************************************************************************/
  107. void LedScan()
  108. {
  109.         static uint8 i = 0;

  110.         P2 = 0Xff;
  111.         switch(i)
  112.         {
  113.                 case 0: LEDS4 = 0;LEDS1 = 1;P2 = 0x7f & LedChar[16];i++;break;
  114.                 case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[val / 10 % 10];i++;break;
  115.                 case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[val % 10];i++;break;
  116.                 case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[17];i = 0;break;

  117.                 default:break;
  118.         }
  119. }

  120. /*******************************************************************************
  121. * 文件名:void InitADC(void)
  122. * 描  述: //初始化 AD 转换
  123. * 功  能:
  124. * 作  者:大核桃
  125. * 版本号:1.0.1(2015.03.03)
  126. *******************************************************************************/
  127. void InitADC(void)
  128. {
  129.         P1ASF = 0xE0; //设置 P1 口为模拟口
  130.         ADC_RES = 0; //清除结果寄存器
  131.         CLK_DIV |= 0x20; //ADRJ 为 1,ADC_RES 存放高两位结果,ADC_RESL 存放低 8 位结果
  132.     //ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START;
  133.         ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START;                    //ADC上电并延时
  134. }

  135. /*******************************************************************************
  136. * 文件名:void adc_isr() interrupt 5 using 1
  137. * 描  述: 中断服务程序
  138. * 功  能:
  139. * 作  者:大核桃
  140. * 版本号:1.0.1(2015.03.03)
  141. *******************************************************************************/
  142. void adc_isr() interrupt 5 using 1
  143. {
  144.         EADC = 1;//开ADC中断

  145.         ADC_CONTR &= !ADC_FLAG; //清除ADC中断标志
  146.         temp = ADC_RES;
  147.         temp <<= 8;
  148.         temp |= ADC_RESL;
  149.         flag_ad2 = 1; //电压采集完成标志
  150.         ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | 5;

  151.         EADC = 0;//关闭中断               

  152. }

  153. /*******************************************************************************
  154. * 文件名:VolTage_Monitor(void);
  155. * 描  述: 电压结果计算
  156. * 功  能:模编程块化
  157. * 作  者:大核桃
  158. * 版本号:1.0.1(2015.03.03)
  159. *******************************************************************************/
  160. uint16 VolTage_Monitor(uint8 times) //AD转换与查表处理程序
  161. {
  162.    if(flag_coll1) //每次采集AD的时间间隔标志位
  163.    {
  164.                 if(ad_count < times)//连续采集16次后再把求总数据的平均值
  165.                 {
  166.                         if(flag_ad2 == 1)   //完成一次AD采样
  167.                         {
  168.                                 flag_ad2 = 0;  //清除完成一次采样的标志位

  169.                                 Adresult_val = Adresult_val + temp;
  170.                                 ad_count++;
  171.                                 ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | 5;
  172.                         }
  173.                 } //右移动一位数据就相当于整除以2
  174.                 else  //已经采集完16次数据,这个时候把总累加数据除以16就可以求得平均值了
  175.                 {
  176.                         Adresult_val >>= 4;
  177.                         val = ((Adresult_val) * 2 * (3.3 / 1023) * 10);//放大10
  178.                         Adresult_val = 0; //AD暂存清零
  179.                         temp = 0;        //把采集AD的结果清零
  180.                         ad_count = 0;     //把采集次数重新清零
  181.                 }
  182.    }

  183.    return  val;//返回采集的电压数值
  184. }

  185. /*******************************************************************************
  186. * 文件名:void Time0_Init()
  187. * 描  述: 定时器0初始化
  188. * 功  能:10毫秒@11.0592MHz
  189. * 作  者:大核桃
  190. * 版本号:1.0.1(2017.05.23)
  191. *******************************************************************************/
  192. void Time0_Init(void)
  193. {
  194.         AUXR |= 0x80;   //定时器时钟1T模式
  195.         TMOD &= 0xF0;        //设置定时器模式
  196.         TL0 = 0xCD;                //设置定时初值
  197.         TH0 = 0xD4;                //设置定时初值
  198.         ET0 = 1;
  199.         TR0 = 1;                //定时器0开始计时
  200.         EA = 1;               
  201. }
  202. /*******************************************************************************
  203. * 文件名:
  204. * 描  述: 中断函数
  205. * 功  能:1毫秒@11.0592MHz
  206. * 作  者:大核桃
  207. * 版本号:1.0.1(2017.05.23)
  208. *******************************************************************************/
  209. void ET0_IRQHandler() interrupt 1
  210. {
  211.         static uint8 tmrcoll1 = 0;//数据采集间隔

  212.         EADC = 0;  //在定时中断中禁止AD中断

  213.     TL0 = 0xCD;                //设置定时初值
  214.         TH0 = 0xD4;                //设置定时初值

  215.         tmrcoll1++;//数据采集时间累加
  216.         if(tmrcoll1 >= 2) // 2 = 2ms                                                                                                                                                                           //166*100 =16.6MS  
  217.         {
  218.             tmrcoll1 = 0;
  219.                 flag_coll1 = 1;//数据采集间隔标志位 2ms读取一次数据
  220.         }

  221.         LedScan();

  222.         EADC = 1;  //在定时中断中打开AD中断
  223.                         
  224. }

  225. /*******************************************************************************
  226. * 文件名:void Mcu_Port_Init()
  227. * 描  述: io初始化
  228. * 功  能:
  229. * 作  者:大核桃
  230. * 版本号:1.0.1(2017.05.23)
  231. *******************************************************************************/
  232. void Mcu_Port_Init()
  233. {
  234.         IE = 0xa8;//允许AD转换
  235.         //将P0口低二位配置为推挽输出
  236.         //234567位配置位高阻输入
  237.         P0M1 = 0xFC;//1111 1100
  238.         P0M0 = 0X03;//0000 0011
  239.         //P0 = 0X01;//第6个
  240.         //P0 = 0X02;//第7个
  241.         //高3位配置高阻输入,用作模拟口
  242.         //其他配置推挽输出,驱动LED
  243.         P1M1 = 0xE0;//1110 0000
  244.         P1M0 = 0X1F;//0001 1111
  245.         //P2口配置准双向口
  246.         P2M1 = 0X00;
  247.         P2M0 = 0X00;
  248.         P2 = 0Xff; //上电为1111 1111


  249. //        //P54,P55口为推挽输出
  250.         P5M1 = 0X00;
  251.         P5M0 = 0X00;
  252.         P5 = 0xFF;

  253.         //P37,P36,3.2,P3.3 P3.4口为推挽输出
  254.         P3M1 = 0X00;
  255.         P3M0 = 0XFC;
  256.         P3 = 0X23; //0010 0111//第5个LED端口
  257.         
  258.         LED0 = 0;//第1组LED,如果使能请置为1
  259.         LED1 = 0;
  260.         LED2 = 0;
  261.         LED3 = 0;
  262.         LED4 = 0;
  263.         LED5 = 0;
  264.         LED6 = 0;
  265.         LED7 = 0;        
  266. }

  267. /*******************************************************************************
  268. * 文件名:void Delay500ms()                //@24.000MHz
  269. * 描  述:Y5内核延时
  270. * 功  能:
  271. * 作  者:大核桃
  272. * 版本号:1.0.1(2017.05.23)
  273. *******************************************************************************/
  274. void Delay500ms()                //@24.000MHz
  275. {
  276.         unsigned char i, j, k;

  277.         _nop_();
  278.         _nop_();
  279.         i = 46;
  280.         j = 153;
  281.         k = 245;
  282.         do
  283.         {
  284.                 do
  285.                 {
  286.                         while (--k);
  287.                 } while (--j);
  288.         } while (--i);
  289. }
  290. </font>


先来介绍一些基本的理论知识,不然的话,可能有些东西无法搞懂。
     关于ADC的参考电压
因为我们的电子时钟是锂电池供电的,电压是3.7V的,我们这里用了一个3.3V的稳压芯片662K,输出3.3V直接作为单片机的电源,也作为ADC的参考电压,这里我们简化了设计,没有用外部的参考电压源,对于一个简单的来说,这样也是可以的。
     关于ADC的位数和分辨率
在这里,我们选择ADC工作在10位方式,10位的ADC,是从0-1023,那么分辨率也就是3.3/1023 = 0.0032258064516129V,大概一个分辨率3mv左右。
     关于转换时间和转换速率
转换时间和转换速率是倒数的关系,所谓的转换时间,指的是ADC从开始启动,到ADC转换完成出结果,这个时间该怎么去计算呢?我们在程序中选择了时钟频率是11.0592MHZ,那么我们ADC的时钟频率也就是11.0592MHZ了,在程序中,我们选择了540个时钟周期完成一个ADC转换,转换速率也就是20KHZ左右,转换时间大约是48US左右
    关于采样频率和采样周期
采样频率和采样周期也是互为倒数的关系,这个和上面的转换时间,转换速率非常容易让人搞迷糊,关于采样频率,有一个采样定理,叫奈奎斯特采样定律,这个定律说的是,采样频率不能低于输入ADC的信号的最高频率的2倍,举个例子,比如上面这样的情况,我们选择540个时钟周期完成一次ADC转换,那么转换速率是20KHZ,那么也就是说,如果我们要保证信号采集的是完整的波形,那么这个输入的信号不能超过10KHZ,你想想看,如果输入的信号大于10KHZ,而你转换速率是20KHZ,如果采样频率小于20KHZ,那么可能你还没有完成一个完整的ADC转换过程,或者采集的波形不是完整的,那么这样的ADC的结果跟实际值比较会存在严重的失真,这样是不被允许的。

   好了,经过以上知识的铺垫,再来看程序代码就应该比较容易懂了,在程序中,我们选择了在ADC进中断前打开EADC,处理完数据后,要关闭EADC这个ADC转换中断使能标志位,防止其他中断或者任务打断ADC的采集。我们在任务中选择的采样频率是500HZ,也就是2MS启动ADC采集依次数据,连续采集16次,因为我们所采集的电压信号的变化频率没有那么快速,所以,我们这里是可以这样用的,当启动了一次AD转换之后,进行计数,如果小于16次,那么一直在IF里面执行,当系统检测到完成一个AD转换,将AD转换完成标志位清零,将读取的相关通道的ADC数值累加,继续采集,如果采集完成了,那么进行取平均运算,然后将结果计算出来,赋值给相关的变量就可以了,然后对相关的变量或者缓冲区清零,这样,显示在数码管上的电压结果是比较稳定的。
   好了,今天就到这里吧,源码奉献上。



     

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| dabing89 发表于 2018-10-12 10:05 | 显示全部楼层
本帖最后由 dabing89 于 2018-10-12 10:08 编辑

                                                                             用定时器来模拟实现PWM实现呼吸灯的效果-----20181012     
众所周知,PWM的应用是及其广泛的,现在很多高速的单片机内部都集成了硬件PWM,使用起来也很简单,配置好频率和装入计数值就可以工作了,但是在一些低成本的场合,我们选择的单片机没有硬件PWM功能模块,但是我们还存在这个需求怎么办呢?这个时候,我们需要用PWM来模拟实现他,但实现PWM必须要了解PWM的原理,这里我们先了解下。
      PWM全称是脉冲宽度调制解调,比如1个小灯,按照500MS亮一次,500MS灭一次,周期是1S,频率是1HZ,在这里,1个周期说明白了就是2个方波,有高电平和低电平组成,在周期固定的情况下,我们通过不断的调整高电平所占的整个周期比例,即所谓的占空比,就可以实现小灯”不是那么亮“的效果,如果连续起来,就可以实现呼吸灯的效果了,先来用定时器0实现小灯500MS闪烁的效果,通过DEBUG来看下波形。我们把下面的代码拷贝进去,看下现象


/********************************************************
*描述:工程模板,点亮led        500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"


/*******************************************************************************
* 文件名:位定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
bit flag500ms = 0;//500ms标志位
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//

/*******************************************************************************
* 文件名:数据类型定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int  uint16;
typedef unsigned long uint32;

/*******************************************************************************
* 文件名:函数前置声明
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/

void Bsp_Power_Init(void);
void Timer0Init(void);

/*******************************************************************************
* 文件名:主循环入口
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
        Bsp_Power_Init();//LED端口初始化
        Timer0Init();
        LED0 = 1;

        while(1)
        {

                if(flag500ms)
                {
                        flag500ms = 0;

                        DATA0 = ~DATA0;//
                }
                        
        }
}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
        P0M1 = 0xFC;
        P0M0 = 0X03;
        P0 = 0X00;

        P1M1 = 0xE0;
        P1M0 = 0X1F;
        P1 = 0X00;


        //P2口开漏输出
        P2M1 = 0XFF;
        P2M0 = 0XFF;
        P2 = 0Xff;
//        //P54,P55口为推挽输出
        P5M1 = 0X00;
        P5M0 = 0X00;
        P5 = 0xFF;

        //P37,P36,3.2,P3.3 P3.4口为推挽输出
        P3M1 = 0X00;
        P3M0 = 0XFC;
        P3 = 0X23;

}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void)                //1毫秒@12MHZ
{
        AUXR &= 0x7f;                    //定时器时钟12T模式
        TMOD &= 0xF0;                    //设置定时器模式
        TMOD |= 0x01;                    //设置定时器模式
        TL0 = (65535 - 1000) % 256;                //设置定时初值
        TH0 = (65535 - 1000) / 256;                //设置定时初值
        ET0 = 1;
        TR0 = 1;                //定时器0开始计时
        EA = 1;
}

/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
        static uint16 tmr500ms = 0;

        TL0 = (65535 - 1000) % 256;                //设置定时初值1ms
    TH0 = (65535 - 1000) / 256;                //设置定时初值

        tmr500ms++;

        if(tmr500ms >= 500)
        {
                tmr500ms = 0;

                flag500ms  = 1;
        }
                                       
}

从DEBUG可以看到,是500MS变化一次,说明我们的设置是对的,但是在这里还是说明一点,我们用的而是STC15W系列的芯片,但是定时器我配置成了12T模式,和STC89C52是一样使用的。既然我们实现了这个500MS高电平,500MS低电平的效果,我们再来实现下200MS亮,800MS灭的效果吧,程序代码如下:
/********************************************************
*描述:工程模板,点亮led        500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"


/*******************************************************************************
* 文件名:位定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//

/*******************************************************************************
* 文件名:数据类型定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int  uint16;
typedef unsigned long uint32;

/*******************************************************************************
* 文件名:函数前置声明
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/

void Bsp_Power_Init(void);
void Timer0Init(void);

/*******************************************************************************
* 文件名:主循环入口
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
        Bsp_Power_Init();//LED端口初始化
        Timer0Init();
        LED0 = 1;

        while(1);

}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
        P0M1 = 0xFC;
        P0M0 = 0X03;
        P0 = 0X00;

        P1M1 = 0xE0;
        P1M0 = 0X1F;
        P1 = 0X00;


        //P2口开漏输出
        P2M1 = 0XFF;
        P2M0 = 0XFF;
        P2 = 0Xff;
//        //P54,P55口为推挽输出
        P5M1 = 0X00;
        P5M0 = 0X00;
        P5 = 0xFF;

        //P37,P36,3.2,P3.3 P3.4口为推挽输出
        P3M1 = 0X00;
        P3M0 = 0XFC;
        P3 = 0X23;

}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void)                //1毫秒@12MHZ
{
        AUXR &= 0x7f;                    //定时器时钟12T模式
        TMOD &= 0xF0;                    //设置定时器模式
        TMOD |= 0x01;                    //设置定时器模式
        TL0 = (65535 - 1000) % 256;                //设置定时初值
        TH0 = (65535 - 1000) / 256;                //设置定时初值
        ET0 = 1;
        TR0 = 1;                //定时器0开始计时
        EA = 1;
}

/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
        static uint16 tmr200ms = 0;
        static bit a = 0;//翻转状态变量


        if(a)
        {
                TL0 = (65535 - 1000) % 256;                //设置定时初值1ms
                TH0 = (65535 - 1000) / 256;                //设置定时初值

                DATA0 = 0;//小灯亮

                tmr200ms++;        
                if(tmr200ms >= 200)
                {
                        tmr200ms = 0;
                        a = 0;
                }
        }
        else
        {
                TL0 = (65535 - 1000) % 256;                //设置定时初值1ms
        TH0 = (65535 - 1000) / 256;                //设置定时初值
                DATA0 = 1;//小灯灭

                tmr200ms++;        
                if(tmr200ms >= 800)
                {
                        tmr200ms = 0;
                        a = 1;
                }        
        }
                                       
}
仿真效果如下图所示:

我们可以看到高电平占到了80,低电平占到了20,但是把代码下载进单片机,怎么不是我们想要的那种状态呢?这里普及一个知识点,前面帖子说过的,人类的眼睛不能分辨这种刷新速度低于10MS的物体,如果物体的刷新速度高于10MS,我们的眼睛就会感觉到明显的闪烁了,所以我们看到了下载进开发板的现象就是亮200MS,灭800MS的效果,但是我们想实现我们想要的那种不是太亮的效果怎么办呢?其实只要把刷新频率高于100HZ就OK了,也就是周期要控制在10MS之内,改变高低电平所占的比例即可实现这样的效果,我们写一个让小灯2MS亮8MS灭的程序,看看啥效果,程序如下:
/********************************************************
*描述:工程模板,点亮led        500MS闪烁 12MHZ
********************************************************/
#include "stc15w.h"


/*******************************************************************************
* 文件名:位定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//

/*******************************************************************************
* 文件名:数据类型定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int  uint16;
typedef unsigned long uint32;

/*******************************************************************************
* 文件名:函数前置声明
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/

void Bsp_Power_Init(void);
void Timer0Init(void);

/*******************************************************************************
* 文件名:主循环入口
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
        Bsp_Power_Init();//LED端口初始化
        Timer0Init();
        LED0 = 1;

        while(1);

}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
        P0M1 = 0xFC;
        P0M0 = 0X03;
        P0 = 0X00;

        P1M1 = 0xE0;
        P1M0 = 0X1F;
        P1 = 0X00;


        //P2口开漏输出
        P2M1 = 0XFF;
        P2M0 = 0XFF;
        P2 = 0Xff;
//        //P54,P55口为推挽输出
        P5M1 = 0X00;
        P5M0 = 0X00;
        P5 = 0xFF;

        //P37,P36,3.2,P3.3 P3.4口为推挽输出
        P3M1 = 0X00;
        P3M0 = 0XFC;
        P3 = 0X23;

}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Timer0Init(void)                //1毫秒@12MHZ
{
        AUXR &= 0x7f;                    //定时器时钟12T模式
        TMOD &= 0xF0;                    //设置定时器模式
        TMOD |= 0x01;                    //设置定时器模式
        TL0 = (65535 - 1000) % 256;                //设置定时初值
        TH0 = (65535 - 1000) / 256;                //设置定时初值
        ET0 = 1;
        TR0 = 1;                //定时器0开始计时
        EA = 1;
}

/*******************************************************************************
* 文件名:void TIME0_INTER(void) interrupt 1
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIME0_INTER(void) interrupt 1
{
        static uint16 tmr200ms = 0;
        static bit a = 0;//翻转状态变量


        if(a)
        {
                TL0 = (65535 - 1000) % 256;                //设置定时初值1ms
        TH0 = (65535 - 1000) / 256;                //设置定时初值

                DATA0 = 0;//小灯亮

                tmr200ms++;        
                if(tmr200ms >= 2)
                {
                        tmr200ms = 0;
                        a = 0;
                }
        }
        else
        {
                TL0 = (65535 - 1000) % 256;                //设置定时初值1ms
        TH0 = (65535 - 1000) / 256;                //设置定时初值
                DATA0 = 1;//小灯灭

                tmr200ms++;        
                if(tmr200ms >= 8)
                {
                        tmr200ms = 0;
                        a = 1;
                }        
        }
                                       
}

将程序下载进板子上,可以很明显的看到小灯变的不是那么亮了,用逻辑分析仪看下,我们的周期是10MS,实现了我们想要的变的不是那么亮的效果,可是距离我们想要的呼吸灯还是没有实现啊?怎么办呢?答案很简单,只要在在定时器中装入不同的初值即可实现这样的效果,不过要实现呼吸灯的效果,一个定时器是不够的,还要再用一个定时器1才可以,写好的程序如下:
/********************************************************
*描述:工程模板 PWM呼吸灯代码
********************************************************/
#include "stc15w.h"


/*******************************************************************************
* 文件名:位定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
bit flag200ms = 0;
bit flag800ms = 0;
sbit LED0 = P1^0;//
sbit DATA0 = P2^0;//
void Bsp_Power_Init(void);
void TIM0_Init(void);
void TIM1_Init(void);

/*******************************************************************************
* 文件名:数据类型定义
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int  uint16;
typedef unsigned long uint32;

uint8 i = 0;
code uint16 PWM_H[] = {
        100,300,500,700,1000,1300,1500,1700,2000,2300,2500,2700,
        3000,3300,3500,3700,4000,4300,4500,4700,5000,5300,5500,
        5700,6000,6300,6500,6700,7000,7300,7500,7700,8000,8300,
        8500,8700,9000,9300,9500,9700,9900 //高电平重装值
};

code uint16 PWM_L[] = {

        9900,9700,9500,9300,9000,8700,8500,8300,8000,7700,7500,
        7300,7000,6700,6500,6300,6000,5700,5500,5300,5000,4700,
        4500,4300,4000,3700,3500,3300,3000,2700,2500,2300,2000,
        1700,1500,1300,1000,700,500,300,100//低电平重装值
};


/*******************************************************************************
* 文件名:主循环入口
* 描  述:
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
        Bsp_Power_Init();//LED端口初始化
        TIM0_Init();
        TIM1_Init();
        LED0 = 1;

        while(1)
        {
                if(flag200ms)
                {
                        flag200ms = 0;

                        DATA0 = 0;
                }

                if(flag800ms)
                {
                        flag800ms = 0;

                        DATA0 = 1;
                }

        }
}

/*******************************************************************************
* 文件名:void Bsp_Power_Init()
* 描  述: 数码管上电显示
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Bsp_Power_Init(void)
{
        P0M1 = 0xFC;
        P0M0 = 0X03;
        P0 = 0X00;

        P1M1 = 0xE0;
        P1M0 = 0X1F;
        P1 = 0X00;


        //P2口开漏输出
        P2M1 = 0XFF;
        P2M0 = 0XFF;
        P2 = 0Xff;
//        //P54,P55口为推挽输出
        P5M1 = 0X00;
        P5M0 = 0X00;
        P5 = 0xFF;

        //P37,P36,3.2,P3.3 P3.4口为推挽输出
        P3M1 = 0X00;
        P3M0 = 0XFC;
        P3 = 0X23;

}

/*******************************************************************************
* 文件名:void Timer0Init(void)
* 描  述: 数定时器0初始化
* 功  能:编程模块化
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIM0_Init(void)
{
        AUXR &= 0x7F;//定时器时钟12T模式
        TMOD &= 0XF0;//配置定时器0为工作模式1
        TMOD |= 0X01;
        TH0 = (65535 - 1000) / 256;//高八位重载值溢出1000次定时1ms
        TL0 = (65535 - 1000) % 256;//低八位重载值
        ET0 = 1;//打开定时器0中断使能位
        TR0 = 1;//打开定时器,使之工作
        EA = 1;//打开总中断        
}

/*******************************************************************************
* 文件名:void TIM1_Init(void)
* 描  述:定时器1初始化配置
* 功  能:初始化
* 作  者:大核桃
* 版本号:1.0.1(2016.07.23)
*******************************************************************************/
void TIM1_Init(void)
{
        AUXR &= 0xBF;//定时器时钟12T模式
        TMOD &= 0X0F;//配置定时器1为工作模式1
        TMOD |= 0X10;
        TH1 = (65535 - 10000) / 256;//高八位重载值溢出1000次定时1ms
        TL1 = (65535 - 10000) % 256;//低八位重载值
        ET1 = 1;//打开定时器1中断使能位
        TR1 = 1;//打开定时器,使之工作
        EA = 1;//打开总中断        
}

/*******************************************************************************
* 文件名:中断服务函数
* 描  述:定时器1中断服务函数
* 功  能:        中断标号对应   参考数据手册560页        
*                        中断名称         
* 作  者:大核桃
* 版本号:1.0.1(2016.11.15)
*******************************************************************************/
void TIM1_IRQ_Handler(void)        interrupt 3
{
        static uint16 tmr50ms = 0;
        static bit a = 0;

        TH1 = (65535 - 10000) / 256;
        TL1 = (65535 - 10000) % 256;//10ms溢出一次

        tmr50ms++;
        if(tmr50ms >= 5)//50ms改变一次PWM重装值
        {
                tmr50ms = 0;

                if(a)
                {
                   i--;
                   if(i == 0)
                   {
                            a = 0;
                   }
        
                }
                else
                {
                        i++;
                        if(i >= 40)
                        {
                                a = 1;
                        }
        
                }
        }
               
}

/*******************************************************************************
* 文件名:void TIMER0_INTER(void) interrupt 1
* 描  述: 中断处理程序
* 功  能:
* 作  者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void TIM0_IRQ_Handler(void)        interrupt 1
{
        static bit index = 0;

        if(index)
        {
                TH0 = (65536 - PWM_H) / 256;
                TL0 = (65536 - PWM_H) % 256;//12MHZ下溢出1000次定时1ms

                flag200ms = 1;
                index = 0;
        }
        else
        {
                TH0 = (65536 - PWM_L) / 256;
                TL0 = (65536 - PWM_L) % 256;//12MHZ下溢出1000次定时1ms
                flag800ms = 1;

                index = 1;        
        }        

}
我们用了定时器1每隔50MS改变1次定时器的初值,做了2个数组,分别存放PWM的高电平计数初值和低电平计数初值,在12MHZ下计数10000个,恰好是10MS,这样我们就实现了呼吸灯的效果,如果你想让呼吸灯变的更平滑更均匀,可以将定时器的初值更加细化就可以了,如果你对这个程序有啥疑问,可以留言,好了,就介绍到这里吧,代码奉献上。






本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
ywwwh 发表于 2020-8-5 20:48 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

28

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部