找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 11851|回复: 22
收起左侧

51单片机入门经验分享5-STC15W1K16PWM内部EEPROM的使用

  [复制链接]
ID:74187 发表于 2018-10-13 08:56 | 显示全部楼层 |阅读模式
STC15W1K16PWM内部EEPROM的使用--20181013

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

000013.jpg
  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次,稍后我们详细的解析下这个程序。
000014.jpg

关于数码管的一些问题
     一个8段的数码管其实就是8个小灯啊,我们知道LED是有方向的,只有加正向偏置电压才会点亮,正极的一端是阳极,负极的一端是阴极,如果我们把所有的阳极连到一个公共点,通过给其阴极一个低电位的方法能够点亮的,叫做共阳极数码管,那么共阴极数码管就是倒过来了,高电平点亮,所有的阴极连在一起,限流电阻是友情提供的,实际是没有的,如下图所示:
000015.jpg 000016.jpg
有人可能觉得,那这8个小灯是如何排列的啊?怎么看呢?客官,您别急,我来画一下,您就明白了。如下图所示,共阳极数码管示意图:
000017.jpg
有了这张图,我们来看一下程序,就好办了,想一想,如果我要在数码管上显示一个数字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,这个段码和位码是如何选择的呢?用万用表的二极管档位,我们知道二极管是单向导电的,我们又知道正向偏置是可以点亮小灯的,不断的变换万用表的表笔,将亮的段位和引脚记下来,按照提供的数码管引脚图就可以分出段码和位码来。
000018.jpg
如果我们要显示一个1,打开对应的IO,那么我们只要对P2赋值P2 = LedChar[1]就好了;可是在实际应用中,我们需要显示的更加复杂,因此,只能这样动态进行赋值了,新建一个counter变量,然后将最低位的数码管显示个位,第二个数码管显示10位,第三个数码管显示百位,第四个数码管显示千位,依次这样,相除取余数即可实现。


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


今天的,就到这里吧,代码献上
006 内部EEPROM使用.rar (47.48 KB, 下载次数: 138)
回复

使用道具 举报

ID:410020 发表于 2018-10-15 16:08 | 显示全部楼层
不错,不错
回复

使用道具 举报

ID:412788 发表于 2018-10-20 16:59 来自手机 | 显示全部楼层
非常有用 多谢楼主
回复

使用道具 举报

ID:423217 发表于 2018-11-8 20:36 | 显示全部楼层
厉害了,我的哥,赞一个
回复

使用道具 举报

ID:430060 发表于 2018-11-20 17:57 | 显示全部楼层
厉害了,我的哥,赞一个
回复

使用道具 举报

ID:76133 发表于 2018-12-24 16:07 | 显示全部楼层
多谢楼主赞一个
回复

使用道具 举报

ID:378965 发表于 2019-1-14 11:14 | 显示全部楼层
共用一个flash 长期测试是否稳定呢?
回复

使用道具 举报

ID:378965 发表于 2019-1-14 11:15 | 显示全部楼层
毕竟共用一个flash 长期测试是否稳定?
回复

使用道具 举报

ID:461889 发表于 2019-1-23 14:51 | 显示全部楼层
多谢楼主
回复

使用道具 举报

ID:412814 发表于 2019-2-1 20:25 | 显示全部楼层
谢谢楼主,学习了。
回复

使用道具 举报

ID:472242 发表于 2019-3-5 00:01 | 显示全部楼层
好案例,值得推荐!
回复

使用道具 举报

ID:514110 发表于 2019-4-16 22:08 来自手机 | 显示全部楼层
谢谢,有用
回复

使用道具 举报

ID:495990 发表于 2019-4-20 08:06 | 显示全部楼层
赞一个
回复

使用道具 举报

ID:248392 发表于 2019-5-10 11:50 | 显示全部楼层
非常有用 多谢楼主
回复

使用道具 举报

ID:482511 发表于 2019-6-11 07:08 来自手机 | 显示全部楼层
谢谢分享,但共用flash稳定吗?
回复

使用道具 举报

ID:495792 发表于 2019-6-11 08:58 | 显示全部楼层
赞一个
回复

使用道具 举报

ID:399179 发表于 2019-11-22 22:04 来自手机 | 显示全部楼层
感谢楼主分享!
回复

使用道具 举报

ID:64931 发表于 2020-3-12 11:11 | 显示全部楼层
多谢分享,正在找这方面资料
回复

使用道具 举报

ID:399179 发表于 2021-1-30 21:09 来自手机 | 显示全部楼层
正在学习,谢谢楼主分享!
回复

使用道具 举报

ID:976070 发表于 2022-2-4 22:13 | 显示全部楼层
相当实用。正在找这方面资料。
回复

使用道具 举报

ID:1021611 发表于 2022-4-25 17:35 | 显示全部楼层
相当不错,很实用的资料
回复

使用道具 举报

ID:1049825 发表于 2022-10-31 20:23 | 显示全部楼层
对论坛和楼主提供的帮助感谢万分,有种茅塞顿开的感觉,谢了!
回复

使用道具 举报

ID:1051419 发表于 2022-11-16 22:40 | 显示全部楼层
counter=IapReadByte(0X0000);
counter++;这两个怎么会报警,那里出错
回复

使用道具 举报

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

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表