找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 15318|回复: 14
打印 上一主题 下一主题
收起左侧

基于51单片机的电表抄读(DLT645-1997规约)

  [复制链接]
跳转到指定楼层
楼主
硬件设计:AT89S52单片机通过MAX232实现电平转换与串口连接。由于电能表采用RS485通信,因此串口与电能表之间必须使用RS232/RS485转换器UT-2216进行连接。
软件设计:实时时间的显示和通信程序设计。要实现实时显示就需要对DS1302时钟芯片和液晶LCD1602进行编程,将DS1302的数据读出来显示在LCD1602第一行上,LCD1602也要显示电能表数据。通信方式为主从站方式通信,单片机为主站,主站需要发送读数据命令帧和接收电能表应答回复的数据,因此,就需要设计单片机发送命令和接收数据的程序。将接收到的数据储存在单片机存储器中,在程序中将其存于数组中。最后把数组中的数据取出来写入液晶LCD1602中,显示在液晶上。

源代码如下:
  1. # include<reg52.h>
  2. # include<intrins.h>   //包含_nop_()函数定义的头文件
  3. # define uchar unsigned char
  4. # define uint  unsigned int
  5. sbit SCLK=P1^0;   //位定义1302芯片的接口,时钟输出端口定义在P1.0引脚
  6. sbit DATA=P1^1;   //位定义1302芯片的接口,数据输出端定义在P1.1引脚
  7. sbit RST=P1^2;    //位定义1302芯片的接口,复位端口定义在P1.2引脚
  8. sbit RS=P2^0;           //寄存器选择位,将RS位定义为P2.0引脚
  9. sbit RW=P2^1;           //读写选择位,将RW位定义为P2.1引脚
  10. sbit E=P2^2;            //使能信号位,将E位定义为P2.2引脚
  11. sbit BF=P0^7;           //忙碌标志位,,将BF位定义为P0.7引脚
  12. uchar RecNum=0;   
  13. uint  cs;//存放校验码
  14. uchar con;//存放控制码
  15. uchar add[6];
  16. uchar idata rdata[6];
  17. uchar code string[ ]= {"Welcome to use"};
  18. uchar code string1[ ]= {"kWh"};
  19. uchar code digit[10]={"0123456789"};   //定义字符数组显示数字
  20. unsigned char code key_code[]={ 0xee,0xde,0xbe,0x7e,0xed,0xdd,0xbd,0x7d,0xeb,0xdb,0xbb,0x7b,0xe7,0xd7,0xb7,0x77}; //键盘编码
  21. uchar  add1[6]={0x53,0x50,0x00,0x00,0x00,0x00};//  存放1#电能表地址码,(地址码已经确定,通过电能表测试软件确定)
  22. uchar  add2[6];//  存放2#电能表地址码
  23. uchar  tdata[2]={0x10,0x90};// 存放要发送的数据
  24. uchar  tdata1[2]={0x20,0x90};
  25. uchar  tdata2[2]={0x10,0x94};
  26. uchar  tdata3[2]={0x20,0x94};
  27. /***************************特殊字符定义************************/
  28. # define PRECURSOR_CHAR 0xfe;//定义前导字符
  29. # define START_CHAR 0x68;//定义帧起始符
  30. # define END_CHAR 0x16;//定义帧结束符
  31. /***************************接收状态定义************************/
  32. # define RS_WAIT  0;//等待状态
  33. # define RS_AWAKEN 1;//唤醒状态
  34. # define RS_ADD 2;//接收地址域状态
  35. # define RS_HEADER 3;//接收帧头状态
  36. # define RS_CON 4;//接收控制码状态
  37. # define RS_DATALEN 5;//接收数据域长度状态
  38. # define RS_DATA 6;//接收数据域状态
  39. # define RS_CHECK 7;//接收校验码状态
  40. # define RS_END 8;//接收结束符状态
  41. /********************************延时若干毫秒 ****************************/
  42. void delaynms(uchar n)
  43. {
  44.    uchar i,j;
  45.    while(n--)
  46.    {
  47. for(i=0;i<10;i++)
  48.    for(j=0;j<33;j++);
  49.     }
  50. }[/color][/size][/align][size=3][color=#000000][font=宋体][align=left]
  51. /*****************************延时若干微秒******************************/
  52. void delaynus(uchar n)
  53. {
  54. uchar i;
  55. for(i=0;i<n;i++);
  56. }
  57.   
  58. /************矩阵键盘程序***********************/[/align][align=left]uchar  keyscan()
  59. {
  60.    uchar  scan1,scan2,keycode;[/align][align=left]   P1=0xf0;
  61.    scan1=P1;
  62.    if((scan1&0xf0)!=0xf0)           //判键是否按下
  63.    {
  64.      delaynms(20);                   //延时20ms
  65.      scan1=P1;
  66.      if((scan1&0xf0)!=0xf0)         //二次判键是否按下
  67.      {
  68.         P1=0x0f;  //线反转法的核心
  69.         scan2=P1;
  70.         keycode=scan1|scan2;         //组合成键编码
  71.   }
  72.       
  73.     return(keycode);
  74.     }
  75.      
  76. }
  77. /**********************判断液晶模块的忙碌状态***********************/
  78. bit BusyTest(void)
  79.   {
  80.         bit result;
  81.      RS=0;       //根据规定,RS为低电平,RW为高电平时,可以读状态
  82.      RW=1;
  83.      E=1;        //E=1,才允许读写
  84.      _nop_();   //空操作
  85.      _nop_();
  86.      _nop_();
  87.      _nop_();   //空操作四个机器周期,给硬件反应时间
  88.      result=BF;  //将忙碌标志电平赋给result
  89.     E=0;         //将E恢复低电平
  90.     return result;
  91.   }
  92. /********************将模式设置指令或显示地址写入液晶模块************************/
  93. void WriteInstruction (uchar dictate)
  94. {   
  95.     while(BusyTest()==1);   //如果忙就等待
  96.   RS=0;                  //根据规定,RS和R/W同时为低电平时,可以写入指令
  97.   RW=0;   
  98.   E=0;                   //E置低电平(根据表8-6,写指令时,E为高脉冲,
  99.                            // 就是让E从0到1发生正跳变,所以应先置"0"
  100.   _nop_();
  101.   _nop_();               //空操作两个机器周期,给硬件反应时间
  102.   P0=dictate;            //将数据送入P0口,即写入指令或地址
  103.   _nop_();
  104.   _nop_();
  105.   _nop_();
  106.   _nop_();               //空操作四个机器周期,给硬件反应时间
  107.   E=1;                   //E置高电平
  108.   _nop_();
  109.   _nop_();
  110.   _nop_();
  111.   _nop_();               //空操作四个机器周期,给硬件反应时间
  112.    E=0;                  //当E由高电平跳变成低电平时,液晶模块开始执行命令
  113. }
  114. /*****************指定字符显示的实际地址*********************/
  115. void WriteAddress(uchar x)
  116. {
  117.      WriteInstruction(x|0x80); //显示位置的确定方法规定为"80H+地址码x"
  118. }
  119. /*********************将数据(字符的标准ASCII码)写入液晶模块**************************/
  120. void WriteData(uchar y)
  121. {
  122.     while(BusyTest()==1);  
  123.    RS=1;           //RS为高电平,RW为低电平时,可以写入数据
  124.    RW=0;
  125.    E=0;            //E置低电平(根据表8-6,写指令时,E为高脉冲,
  126.                      // 就是让E从0到1发生正跳变,所以应先置"0"
  127.    P0=y;           //将数据送入P0口,即将数据写入液晶模块
  128.    _nop_();
  129.    _nop_();
  130.     _nop_();
  131.      _nop_();       //空操作四个机器周期,给硬件反应时间
  132.    E=1;           //E置高电平
  133.    _nop_();
  134.    _nop_();
  135.    _nop_();
  136.   _nop_();        //空操作四个机器周期,给硬件反应时间
  137.   E=0;            //当E由高电平跳变成低电平时,液晶模块开始执行命令
  138. }[/align][align=left]/*********************向1302写一个字节数据******************************/
  139. void Write1302(uchar dat)
  140. {
  141.   uchar i;
  142.   SCLK=0;            //拉低SCLK,为脉冲上升沿写入数据做好准备
  143.   delaynus(2);       //稍微等待,使硬件做好准备
  144.   for(i=0;i<8;i++)      //连续写8个二进制位数据
  145.     {
  146.       DATA=dat&0x01;    //取出dat的第0位数据写入1302  低位在前,高位在后
  147.    delaynus(2);       //稍微等待,使硬件做好准备
  148.    SCLK=1;           //上升沿写入数据
  149.    delaynus(2);      //稍微等待,使硬件做好准备
  150.    SCLK=0;           //重新拉低SCLK,形成脉冲
  151.    dat>>=1;          //将dat的各数据位右移1位,准备写入下一个数据位
  152.      }
  153. }
  154. /********************根据命令字,向1302写一个字节数据 Cmd,储存命令字;dat,储存待写的数据***************/
  155. void WriteSet1302(uchar Cmd,uchar dat)
  156. {   
  157.         RST=0;           //禁止数据传递
  158.         SCLK=0;          //确保写数居前SCLK被拉低
  159.      RST=1;           //启动数据传输
  160.      delaynus(2);     //稍微等待,使硬件做好准备
  161.      Write1302(Cmd);  //写入命令字
  162.      Write1302(dat);  //写数据
  163.      SCLK=1;          //将时钟电平置于高电平状态
  164.      RST=0;           //禁止数据传递
  165. }
  166. /************************从1302读一个字节数据***************************/
  167. uchar Read1302(void)
  168. {
  169.     uchar i,dat;
  170. delaynus(2);       //稍微等待,使硬件做好准备
  171. for(i=0;i<8;i++)   //连续读8个二进制位数据
  172.   {  dat>>=1;
  173.    if(DATA==1)    //如果读出的数据是1
  174.    dat|=0x80;    //将1取出,写在dat的最高位
  175.    SCLK=1;       //将SCLK置于高电平,为下降沿读出
  176.    delaynus(2);  //稍微等待
  177.    SCLK=0;       //拉低SCLK,形成脉冲下降沿
  178.    delaynus(2);  //稍微等待
  179.    }  
  180.   return dat;        //将读出的数据返回
  181. }  
  182. /*****************从1302读取一个字节数据**********************/
  183. uchar  ReadSet1302(uchar Cmd)
  184. {
  185.   uchar dat;
  186.   RST=0;                 //拉低RST
  187.   SCLK=0;                //确保写数居前SCLK被拉低
  188.   RST=1;                 //启动数据传输
  189.   Write1302(Cmd);       //写入命令字
  190.   dat=Read1302();       //读出数据
  191.   SCLK=1;              //将时钟电平置于已知状态
  192.   RST=0;               //禁止数据传递
  193.   return dat;          //将读出的数据返回
  194. }
  195. /************************显示秒***********************/
  196. void DisplaySecond(uchar x)
  197. {
  198. uchar i,j;     //j,k分别储存十位和个位
  199. i=x/10;//取十位
  200. j=x%10;//取个位     
  201. WriteAddress(0x0e);    //写显示地址,将在第1行第15列开始显示
  202. WriteData(digit[i]);    //将百位数字的字符常量写入LCD
  203. WriteData(digit[j]);    //将十位数字的字符常量写入LCD
  204. delaynms(50);         //延时1ms给硬件一点反应时间   
  205. }[/i][/align][align=left][i]/*************************显示分钟*********************/
  206. void DisplayMinute(uchar x)
  207. {
  208. unsigned char i,j;     //j,k十位和个位
  209. i=x/10;//取十位
  210. j=x%10;//取个位     
  211. WriteAddress(0x0b);    //写显示地址,将在第1行第12列开始显示
  212. WriteData(digit[i]);    //将百位数字的字符常量写入LCD
  213. WriteData(digit[j]);    //将十位数字的字符常量写入LCD
  214. delaynms(50);         //延时1ms给硬件一点反应时间   
  215. }
  216. /*************************显示小时********************/
  217. void DisplayHour(uchar x)
  218. {
  219. uchar i,j;     //j,k十位和个位
  220. i=x/10;//取十位
  221. j=x%10;//取个位     
  222. WriteAddress(0x08);    //写显示地址,将在第1行第9列开始显示
  223. WriteData(digit[i]);    //将百位数字的字符常量写入LCD
  224. WriteData(digit[j]);    //将十位数字的字符常量写入LCD
  225. delaynms(50);         //延时1ms给硬件一点反应时间   
  226. }
  227. /*******************显示日*********************/
  228. void DisplayDay(uchar x)
  229. {
  230.   uchar i,j;     //j,k十位和个位
  231. i=x/10;//取十位
  232. j=x%10;//取个位     
  233. WriteAddress(0x06);    //写显示地址,将在第1行第7列开始显示
  234. WriteData(digit[i]);    //将十位数字的字符常量写入LCD
  235. WriteData(digit[j]);    //将个位数字的字符常量写入LCD
  236. delaynms(50);         //延时1ms给硬件一点反应时间   
  237. }
  238. /************************显示月**************/
  239. void DisplayMonth(uchar x)
  240. {
  241. uchar i,j;     //j,k分别储存十位和个位
  242. i=x/10;//取十位
  243. j=x%10;//取个位     
  244. WriteAddress(0x03);    //写显示地址,将在第1行第4列开始显示
  245. WriteData(digit[i]);    //将十位位数字的字符常量写入LCD
  246. WriteData(digit[j]);    //将个位数字的字符常量写入LCD
  247. delaynms(50);         //延时1ms给硬件一点反应时间   
  248. }
  249. /*************************显示年**************/
  250. void DisplayYear(uchar x)
  251. {
  252.     uchar i,j;     //j,k分别储存十位和个位
  253. i=x/10;//取十位
  254. j=x%10;//取个位     
  255. WriteAddress(0x00);    //写显示地址,将在第1行第1列开始显示
  256. WriteData(digit[i]);    //将十位位数字的字符常量写入LCD
  257. WriteData(digit[j]);    //将个位数字的字符常量写入LCD
  258. delaynms(50);         //延时1ms给硬件一点反应时间   
  259. }[/i][/i][/i][/i][/i][/i][/align][align=left][i][i][i] /********************时间显示********************/
  260. void display_time(void)
  261. {
  262.      
  263.    uchar second,minute,hour,day,month,year;      //分别储存秒、分、小时,日,月,年
  264.       uchar ReadValue;   //储存从1302读取的数据
  265.    WriteAddress(0x02);  //写年月分隔符的显示地址, 显示在第1行第3列
  266.       WriteData('-');      //将字符常量写入LCD
  267.       WriteAddress(0x05);  //写月日分隔符的显示地址, 显示在第1行第12列
  268.       WriteData('-');      //将字符常量写入LCD
  269.       WriteAddress(0x0a);  //写小时与分钟分隔符的显示地址, 显示在第1行第6列
  270.       WriteData(':');      //将字符常量写入LCD
  271.       WriteAddress(0x0d);  //写分钟与秒分隔符的显示地址, 显示在第1行第14列
  272.       WriteData(':');      //将字符常量写入LCD
  273.    ReadValue = ReadSet1302(0x81);   //从秒寄存器读数据
  274.       second=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F);//将读出数据转化
  275.    DisplaySecond(second);          //显示秒
  276.    ReadValue = ReadSet1302(0x83);  //从分寄存器读
  277.       minute=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //将读出数据转化
  278.    DisplayMinute(minute);        //显示分
  279.       ReadValue = ReadSet1302(0x85);  //从分寄存器读
  280.       hour=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //将读出数据转化
  281.    DisplayHour(hour);              //显示小时
  282.       ReadValue = ReadSet1302(0x87);  //从分寄存器读
  283.       day=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //将读出数据转化
  284.    DisplayDay(day);                //显示日
  285.    ReadValue = ReadSet1302(0x89);  //从分寄存器读
  286.       month=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //将读出数据转化
  287.    DisplayMonth(month);            //显示月
  288.    ReadValue = ReadSet1302(0x8d);  //从分寄存器读
  289.       year=((ReadValue&0xf0)>>4)*10 + (ReadValue&0x0F); //将读出数据转化
  290.    DisplayYear(year);             //显示年[/i][/i][/i][/align][align=left][i][i][i]
  291. }
  292. /*****************数据解析*********************/
  293. void message_proc()
  294. {
  295.     uchar mm,m,n,i;[/i][/i][/i][/align][align=left][i][i][i] WriteAddress(0x42);  // 设置显示位置
  296. for(i=5;i>=3;i--)
  297.     {
  298.     mm=((rdata[i]&0x70)>>4)*10 + (rdata[i]&0x0F);
  299.     m=mm/10;
  300. n=mm%10;
  301. WriteData(digit[m]);
  302. WriteData(digit[n]);
  303. delaynms(100);
  304. }
  305. WriteData('.');
  306. delaynms(100);
  307.     mm=((rdata[2]&0x70)>>4)*10 + (rdata[2]&0x0f);//将BCD码转化成字符
  308.     m=mm/10; //取十位
  309. n=mm%10; //取个位
  310. WriteData(digit[m]);
  311. WriteData(digit[n]); //显示在LCD1602上
  312. delaynms(100);
  313.     for(i=0;i<3;i++)   
  314. WriteData(string1[i]);
  315. delaynms(100);
  316. }
  317. /***************************1302初始化********************/
  318. void Init_DS1302(void)
  319. {[/i][/i][/i][/i][/i][/i][/align][align=left][i][i][i]   /*--------------------这是每次都初始化的语句-----------------*/
  320.   
  321.    /*  
  322.   
  323.    WriteSet1302(0x8E,0x00);                 //根据写状态寄存器命令字,写入不保护指令
  324.    WriteSet1302(0x80,((20/10)<<4|(20%10)));   //根据写秒寄存器命令字,写入秒的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x82,((28/10)<<4|(28%10)));   //根据写分寄存器命令字,写入分的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x84,((10/10)<<4|(10%10))); //根据写小时寄存器命令字,写入小时的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x86,((10/10)<<4|(10%10))); //根据写日寄存器命令字,写入日的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x88,((5/10)<<4|(5%10))); //根据写月寄存器命令字,写入月的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x8c,((13/10)<<4|(13%10)));   //根据写年寄存器命令字,写入年的初始值[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x90,0xa5);                //打开充电功能 选择2K电阻充电方式[/i][/i][/i][/align][align=left][i][i][i] WriteSet1302(0x8E,0x80);              //根据写状态寄存器命令字,写入保护指令
  325. */
  326.     uchar flag;
  327.    
  328.     flag= ReadSet1302(0x81);
  329. if(flag&0x80)
  330. {      //判断时钟芯片是否关闭
  331.     WriteSet1302(0x8E,0x00);                 //根据写状态寄存器命令字,写入不保护指令
  332.     WriteSet1302(0x80,((10/10)<<4|(10%10)));   //根据写秒寄存器命令字,写入秒的初始值
  333. WriteSet1302(0x82,((28/10)<<4|(28%10)));   //根据写分寄存器命令字,写入分的初始值
  334. WriteSet1302(0x84,((10/10)<<4|(10%10))); //根据写小时寄存器命令字,写入小时的初始值
  335. WriteSet1302(0x86,((10/10)<<4|(10%10))); //根据写日寄存器命令字,写入日的初始值
  336. WriteSet1302(0x88,((5/10)<<4|(5%10))); //根据写月寄存器命令字,写入月的初始值
  337. WriteSet1302(0x8c,((13/10)<<4|(13%10)));  //根据写年寄存器命令字,写入年的初始值
  338. WriteSet1302(0x90,0xa5);                //打开充电功能 选择2K电阻充电方式
  339. WriteSet1302(0x8E,0x80);      //根据写状态寄存器命令字,写入保护指令
  340.   }
  341.       

  342.      //如果不想每次都初始化时间,也就是掉电后还想让时钟继续走时的话 就用上面的语句   [/i][/i][/i][/align][align=left][i][i][i]  }
  343. /***************串口初始化*********************/
  344. void initial_serial(void)
  345. {[/i][/i][/i][/align][align=left][i][i][i]  TMOD=0x20;   // 定时器1工作于8位自动重载模式(工作方式2), 用于产生波特率
  346.   TH1=TL1=0xe8;   // 波特率为1200bps
  347.   TF1=0;    // 计数未满
  348.   TR1=1;          //启动定时器1
  349.   SCON=0xd0;   // 设定串行口工作方式3(11位异步收发方式) ,允许接收数据
  350.   PCON=0x00;            //波特率不加倍                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 //波特率不加倍(SMOD=0)
  351.   ES=1;     //开串行口中断
  352.   EA=1;                 //开总中断[/i][/i][/i][/align][align=left][i][i][i]}
  353. /*************************LCD初始化**************************/
  354. void LcdInitiate(void)
  355. {
  356.     delaynms(15);               //延时15ms,首次写指令时应给LCD一段较长的反应时间
  357.     WriteInstruction(0x38);     //显示模式设置:16×2显示,5×7点阵,8位数据接口
  358. delaynms(5);                //延时5ms ,给硬件一点反应时间
  359.     WriteInstruction(0x38);
  360. delaynms(5);               //延时5ms ,给硬件一点反应时间
  361. WriteInstruction(0x38);     //连续三次,确保初始化成功
  362. delaynms(5);               //延时5ms ,给硬件一点反应时间
  363. WriteInstruction(0x0c);     //显示模式设置:显示开,无光标,光标不闪烁
  364. delaynms(5);               //延时5ms ,给硬件一点反应时间
  365. WriteInstruction(0x06);     //显示模式设置:光标右移,字符不移
  366. delaynms(5);                //延时5ms ,给硬件一点反应时间
  367. WriteInstruction(0x01);     //清屏幕指令,将以前的显示内容清除
  368. delaynms(5);             //延时5ms ,给硬件一点反应时间[/i][/i][/i][/align][align=left][i][i][i] }
  369. /***************单片机(主站)发送函数********************/
  370. void send(uchar * add,uchar con1,uchar DataLen1,uchar *tdata)
  371. {
  372.   uchar  sh,j,m,ecc=0;  //定义变量
  373.   TI=0;// 发送中断标志复位
  374.   sh=START_CHAR;//发送帧起始符0x68;
  375.   ACC=sh;
  376.   TB8=P;
  377.   SBUF=sh;
  378.   ecc=ecc+sh;//计算校验和 (校验和指从帧起始符到检验码之前所有各字节二进制和)
  379.   while(!TI);
  380.   TI=0;
  381.   for(j=0;j<6;j++)
  382.   {
  383.     sh=*(add+j);//发送地址
  384.     ACC=sh;
  385.     TB8=P;
  386.     SBUF=sh;
  387.     ecc=ecc+sh;//再一次计算校验和
  388.     while(!TI);
  389.     TI=0;
  390.    }
  391.   sh=START_CHAR;//发送帧起始符0x68;
  392.   ACC=sh;
  393.   TB8=P;
  394.   SBUF=sh;
  395.   ecc=ecc+sh;//计算校验和 (校验和指从帧起始符到检验码之前所有各字节二进制和)
  396.   while(!TI);
  397.   TI=0;
  398.   sh=con1;//发送控制码
  399.   ACC=sh;
  400.   TB8=P;
  401.   SBUF=sh;
  402.   ecc=ecc+sh;//计算校验和
  403.   while(!TI);
  404.   TI=0;
  405.   sh=DataLen1; //发送数据长度
  406.   ACC=sh;
  407.   TB8=P;
  408.   SBUF=sh;
  409.   ecc=ecc+sh;//计算校验和
  410.   while(!TI);
  411.   TI=0;
  412.   for(m=0;m<DataLen1;m++)
  413.   {
  414.    sh=(*(tdata+m)+0x33);// 发送数据标识DI0 DI1,传输时发送方按字节进行加33H 处理
  415.    ACC=sh;
  416.    TB8=P;
  417.    SBUF=sh;
  418.    ecc=ecc+sh;//计算校验和
  419.    while(!TI);
  420.    TI=0;
  421.    }
  422.    sh=ecc;
  423.    ACC=sh;
  424.    TB8=P;
  425.    SBUF=sh;
  426.    while(!TI);
  427.    TI=0;
  428.    ecc=0;//校验和清0
  429.    sh=END_CHAR; //发送帧结束符0x16
  430.    ACC=sh;
  431.    TB8=P;
  432.    SBUF=sh;
  433.    while(!TI);
  434.    TI=0;
  435.   [/i][/i][/i][/align][align=left][i][i][i]}                    

  436. /********************主函数********************/
  437. void main(void)
  438. {
  439.   uchar tt,i;
  440.   LcdInitiate();      //液晶初始化
  441.   initial_serial();  //串口初始化
  442.   Init_DS1302();    //DS1302初始化
  443.   WriteAddress(0x01);  // 设置显示位置为第一行的第1个字
  444.   i = 0;
  445.   while(string[i] != '\0')    //'\0'是数组结束标志
  446. {     
  447. WriteData(string[i]);
  448. i++;
  449. delaynms(20);

  450. }

  451.   WriteAddress(0x42);  // 设置显示位置为第二行的第5个字
  452.   for(i=0;i<12;i++)
  453.   WriteData('-'); //显示在LCD1602上
  454.   while(1)
  455.   {
  456.      P1=0xf0;
  457.   if((P1&0xf0)!=0xf0)
  458. {
  459.        tt=keyscan();
  460.     switch (tt)
  461.     {
  462.      case 0xe7:  send(&add1,0x01,0x02,&tdata);display_time();break;//发送读正向有功总电能命令帧
  463.      case 0xd7:  send(&add1,0x01,0x02,&tdata1);display_time();break; //读反向有功总电能
  464.   case 0xb7: send(&add1,0x01,0x02,&tdata2);display_time();break;  //读上月正向有功总电能
  465.   case 0x77:  send(&add1,0x01,0x02,&tdata3);display_time();break; //读上月反向有功总电能
  466.     }
  467. }
  468. message_proc();
  469. [/i][/i][/i][/i][/i][/align][align=left][i][i][i]  }

  470. }[/i][/i][/i][/align][/font][/color][/size][align=left][size=3][color=#000000][font=宋体][i][i][i]/********************单片机(主站接收)串行口中断函数********************/
  471. void serial() interrupt 4
  472. {
  473.    uchar ch;//存放当前接收的字符
  474.    static uchar ecc,flag=0;
  475.    static uchar AddCount;//地址域计数器
  476.    static uchar DataCount;//数据域计数器
  477.    static uchar DataLen;//数据域长度
  478.    static uchar RecState=RS_WAIT;//串口初始接收状态为等待
  479.    if(RI) //接收中断
  480.    {
  481.      RI=0;//中断标志复位
  482.   ch=SBUF;// 将接收的字符存入ch
  483.   ACC=ch;
  484.   if(RB8==P) //判断偶校验
  485.   {
  486.    if(flag==1)
  487.    ecc=ecc+ch;//计算校验和
  488.   }
  489. else
  490.     RecState=RS_WAIT;//如果偶校验错误,返回等待状态
  491. if(RecNum!=0) return;//如果主程序正在处理接收缓冲区内容,返回
  492. switch(RecState)  //根据当前状态和接收字节判断接收状态
  493. {
  494.    case 0://等待状态
  495.    if(ch==0xfe) //前导字符
  496.    {
  497.      RecState++;
  498.   flag=1;//从下一个状态开始计算校验和
  499.   
  500.    }
  501.    break;
  502.    case 1:    //唤醒状态
  503.    if(ch==0x68)   //如果接收帧起始符68H
  504.    {
  505.     RecState++;        //进入接收地址域状态
  506.         AddCount=0;
  507.    }
  508.    else
  509.    {
  510.     flag=0;
  511.     ecc=0;
  512.     RecState=RS_WAIT;//返回等待状态
  513.    }
  514.    break;
  515.    case 2: //接收地址域状态
  516.    *(add+AddCount)=ch;
  517.    AddCount++;
  518.    if(AddCount==6)//如果接受完地址域6个字节
  519.    {
  520.      AddCount=0;// 地址域计数器复位
  521.   RecState++;
  522.     }
  523.       break;
  524.    case 3:  //接收帧头状态
  525.    if(ch==0x68)
  526.    RecState++;
  527.    break;
  528.    case 4:  //接收控制码状态
  529.    con=ch;//保存控制码
  530.    RecState++;
  531.    break;
  532.    case 5: //接收数据域长度状态
  533.    DataLen=ch;//取出数据域长度
  534.    if(DataLen==0)
  535.    RecState=RecState+2;//如果数据域长度为0,直接进入校验和状态
  536.    else
  537.    {
  538.     RecState++;
  539.     DataCount=0; //赋初值
  540.    }
  541.    break;
  542.    case 6:   //接收数据状态
  543.    *(rdata+DataCount)=ch-0x33; //保存数据在rdata[10]数组中
  544.    DataCount++;
  545.    if(DataCount==DataLen) //接收完DataLen个数据
  546.    {
  547.     RecState++;
  548.     DataCount=0;//数据域计数器复位,准备接收下一帧数据
  549.     flag=0; //下一个状态停止计算检验和
  550.    }
  551.    break;
  552.    case  7:
  553.    cs=ch;//保存校验和
  554.    ecc=ecc^cs;
  555.    if(ecc==0)//校验和正确
  556.    {
  557.     RecState++;
  558.     ecc=0;
  559.    }
  560.    else
  561.    RecState=RS_WAIT;  //如果校验和错误,返回等待状态
  562.    break;
  563.    case  8: //接收结束符
  564.    if(ch==0x16) //如果接收帧结束符,表明这一帧接收正确
  565.    {
  566.     RecNum=DataLen;//取出字节数
  567.   DataLen=0;

  568.    }
  569.    RecState=RS_WAIT;// 返回等待状态
  570.    break;
  571.     }
  572.   }
  573. }
复制代码

基于 单片机开发板读表系统仿真电路.PDF

31.83 KB, 下载次数: 124, 下载积分: 黑币 -5

评分

参与人数 3黑币 +110 收起 理由
海水千山 + 5 赞一个!
宁采尘 + 5 很给力!
admin + 100 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏13 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
ID:209219 发表于 2017-6-8 15:48 | 只看该作者
楼主方便加下QQ吗,有些问题想请教,875576671谢谢!
回复

使用道具 举报

板凳
ID:214844 发表于 2017-6-26 17:00 | 只看该作者
新手很多不懂
回复

使用道具 举报

地板
ID:217617 发表于 2017-7-7 13:36 | 只看该作者
很实用的作品
回复

使用道具 举报

5#
ID:157057 发表于 2017-7-7 23:05 | 只看该作者
感谢分享,51黑有你更精彩!!!
回复

使用道具 举报

6#
ID:249957 发表于 2017-11-16 10:21 | 只看该作者
多谢分享!感觉这个51论坛还是有很多好文章呢。
回复

使用道具 举报

7#
ID:301327 发表于 2018-4-3 10:17 | 只看该作者
楼主,方便说一下用的哪一种电能表吗
回复

使用道具 举报

8#
ID:51443 发表于 2018-4-6 13:55 | 只看该作者
菩提树下 发表于 2018-4-3 10:17
楼主,方便说一下用的哪一种电能表吗

通用(DLT645-1997规约),但无用,表不让读。
回复

使用道具 举报

9#
ID:311865 发表于 2018-4-21 16:44 | 只看该作者
电量采集装置有什么推荐的吗?直接用一块电表?
回复

使用道具 举报

10#
ID:313732 发表于 2018-4-23 13:30 | 只看该作者
怎么回事

错误.PNG (103.22 KB, 下载次数: 141)

错误.PNG
回复

使用道具 举报

11#
ID:69408 发表于 2019-12-24 10:16 | 只看该作者
感谢分享,51黑有你更精彩!!!
回复

使用道具 举报

12#
ID:29579 发表于 2021-10-3 17:19 | 只看该作者
感谢分享,51黑有你更精彩!!!
回复

使用道具 举报

13#
ID:137736 发表于 2022-11-2 10:30 | 只看该作者
贴的程序中间乱码有点多
回复

使用道具 举报

14#
ID:137736 发表于 2022-11-2 10:31 | 只看该作者

程序中间乱码需要删除
回复

使用道具 举报

15#
ID:195496 发表于 2022-11-3 13:16 | 只看该作者
电度表的地址怎么确定的?
回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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