有时候需要编写简单的测试软件,使用VC++6.0,研究了两种方式操作串口,VC 串口编程方法分为利用 VC 串口控件(或 VC 串口类)和直接调用Windows底层API函数(我称之为VC API 串口编程)两种方法
在Windows 32位以上操作系统(Win98以上)中,将串口(包括其它通信设备)作为文件来处理,所以串口的打开、读写和关闭所用API函数与文件操作函数一样。所以打开串口用CreateFile,读串口用ReadFile,写串口用WriteFile,关闭串口用CloseHandle。
电力行业国网645协议帧结构定义在头文件userdefine.h中
1 typedef unsigned char uint8_t; 2 3 /* 以下是对645规约中的数据帧做的结构体封装 */ 4 struct frame645 { 5 6 uint8_t frame_start_flag1; //帧起始符 7 uint8_t meter_addr[6]; //地址域 8 uint8_t frame_start_flag2; //帧起始符 9 //控制码 10 union 11 { 12 uint8_t control_byte; 13 struct{ 14 uint8_t function_flag:5;//功能码,内容见下 15 //00000:保留 16 //01000:广播校时 17 //10001:读数据 18 //10010:读后续数据 19 //10011:读通信地址 20 //10100:写数据 21 //10101:写通信地址 22 //10110:冻结命令 23 //10111:更改通信速率 24 //11000:修改密码 25 //11001:最大需量清零 26 //11010:电表清零 27 //11011:事件清零 28 uint8_t following_flag:1;//后续帧标志,0:无后续数据帧,1:有后续数据帧 29 uint8_t exception_flag:1;//从站应答标志,0:从站正确应答,1:从站异常应答 30 uint8_t direction_flag:1;//传送方向,0:主站发出的命令帧,1:从站发出的应答帧 31 }control_bits; 32 }control_code; 33 uint8_t datalen;//数据域长度L 34 uint8_t data[50];//数据域+校验码+结束符 35 };
主程序操作代码如下:
1 #include <stdio.h> 2 #include <string.h> 3 #include "userdefine.h" 4 static uint8_t calculate_cs(uint8_t *buf, int len); 5 void SetDLT645_ZXYG(void); 6 7 struct frame645 DLT647_97; 8 int main(int argc, char* argv[]) 9 { 10 FILE* pFile; 11 char lpBuf[]="Hello World!"; 12 DLT647_97.frame_start_flag1=0x68; 13 DLT647_97.frame_start_flag2=0x68; 14 memset(&DLT647_97.meter_addr[0],0xAA,6); 15 DLT647_97.control_code.control_byte=0x01; 16 DLT647_97.datalen=0x02;//2007版本为0x04 17 DLT647_97.data[0]=0x10; 18 DLT647_97.data[1]=0x90; 19 DLT647_97.data[2]=calculate_cs((uint8_t *)&DLT647_97,12); 20 DLT647_97.data[3]=0x16; 21 22 pFile=fopen("COM2","w"); 23 if (pFile==NULL) 24 { 25 return 1; 26 } 27 //fwrite(lpBuf,sizeof(char),strlen(lpBuf),pFile); 28 fwrite((uint8_t *)&DLT647_97,sizeof(char),14,pFile); 29 fclose(pFile); 30 31 return 0; 32 } 33 34 35 /* 计算总加校验和 */ 36 static uint8_t calculate_cs(uint8_t *buf, int len) 37 { 38 int i = 0; 39 uint8_t cs = 0; 40 41 for(i = 0; i < len; i++) 42 { 43 cs += buf[i]; 44 } 45 return(cs); 46 }
接下来看第二种方法:
#include <Windows.h> #include <stdio.h> HANDLE hCom; int main(void) { COMMTIMEOUTS TimeOuts; DCB dcb; DWORD wCount;//读取的字节数 BOOL bReadStat; char str[50]={0}; hCom=CreateFile(TEXT("COM3"),//COM2口 GENERIC_READ|GENERIC_WRITE, //允许读和写 0, //独占方式 NULL, OPEN_EXISTING, //打开而不是创建 0, //同步方式 NULL); if(hCom==(HANDLE)-1) { printf("打开COM失败!\n"); return FALSE; } else { printf("COM打开成功!\n"); } SetupComm(hCom,1024,1024); //输入缓冲区和输出缓冲区的大小都是1024 //设定读超时 TimeOuts.ReadIntervalTimeout=1000; TimeOuts.ReadTotalTimeoutMultiplier=500; TimeOuts.ReadTotalTimeoutConstant=5000; //设定写超时 TimeOuts.WriteTotalTimeoutMultiplier=500; TimeOuts.WriteTotalTimeoutConstant=2000; SetCommTimeouts(hCom,&TimeOuts); //设置超时 GetCommState(hCom,&dcb); dcb.BaudRate=9600; //波特率为9600 dcb.ByteSize=8; //每个字节有8位 dcb.Parity=ODDPARITY;//偶校验 //NOPARITY; //无奇偶校验位 dcb.StopBits=ONESTOPBIT; //-->ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS 1停止位 1.5停止位 2停止位 SetCommState(hCom,&dcb); SetDLT645_ZXYG(); bReadStat=WriteFile(hCom,(uint8_t *)&DLT647_97,14,&wCount,NULL); while(1) { #if 1 PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR); //清空缓冲区 //printf("%s\n",str); bReadStat=ReadFile(hCom,str,23,&wCount,NULL); //9:要读入的字节数,wCount指向实际读取字节数的指针,比如要读取50个,实际可能只读取了49个 if(!bReadStat) { printf("读串口失败!"); return FALSE; } else { //str[8]='\0'; printf("%x\n",str); } #endif Sleep(100); } } /* 计算总加校验和 */ static uint8_t calculate_cs(uint8_t *buf, int len) { int i = 0; uint8_t cs = 0; for(i = 0; i < len; i++) { cs += buf[i]; } return(cs); } void SetDLT645_ZXYG(void) { DLT647_97.frame_start_flag1=0x68; DLT647_97.frame_start_flag2=0x68; memset(&DLT647_97.meter_addr[0],0xAA,6); DLT647_97.control_code.control_byte=0x01; DLT647_97.datalen=0x02;//2007版本为0x04 DLT647_97.data[0]=0x10+0x33; DLT647_97.data[1]=0x90+0x33; DLT647_97.data[2]=calculate_cs((uint8_t *)&DLT647_97,12); DLT647_97.data[3]=0x16; }
附录645协议解析:
1 //DLT645-1997 规约 2 //常见报文解析 3 //2012.9.29 by legend 4 //V1.1 5 6 TX://读(当前)正向有功总电能 7 发送的原始报文 68 03 00 00 00 00 00 68 01 02 43 C3 DC 16 [用于发送] 8 数据域减33报文 68 03 00 00 00 00 00 68 01 02 10 90 xx 16 [用于理解] 9 RX://28 01 00 00 = 128 = 1.28 kWh 10 接收的原始报文 68 03 00 00 00 00 00 68 81 06 43 C3 5B 34 33 33 55 16 11 数据域减33报文 68 03 00 00 00 00 00 68 81 06 10 90 28 01 00 00 xx 16 12 13 TX://读(当前)反向有功总电能 14 发送的原始报文 68 03 00 00 00 00 00 68 01 02 53 C3 EC 16 [用于发送] 15 数据域减33报文 68 03 00 00 00 00 00 68 01 02 20 90 xx 16 [用于理解] 16 RX://28 00 00 00 = 28 = 0.28 kWh 17 接收的原始报文 68 03 00 00 00 00 00 68 81 06 53 C3 5B 33 33 33 64 16 18 数据域减33报文 68 03 00 00 00 00 00 68 81 06 20 90 28 00 00 00 xx 16 19 20 TX://读(当前)正向无功总电能 21 发送的原始报文 68 03 00 00 00 00 00 68 01 02 43 C4 DD 16 22 数据域减33报文 68 03 00 00 00 00 00 68 01 02 10 91 xx 16 23 RX://28 10 00 00 = 1028 = 10.28 kvarh 24 接收的原始报文 68 03 00 00 00 00 00 68 81 06 43 C4 5B 43 33 33 65 16 25 数据域减33报文 68 03 00 00 00 00 00 68 81 06 10 91 28 10 00 00 xx 16 26 27 TX://读(当前)反向无功总电能 28 发送的原始报文 68 03 00 00 00 00 00 68 01 02 53 C4 ED 16 29 数据域减33报文 68 03 00 00 00 00 00 68 01 02 20 91 xx 16 30 RX://29 00 00 00 = 29 = 0.29 kvarh 31 接收的原始报文 68 03 00 00 00 00 00 68 81 06 53 C4 5C 33 33 33 66 16 32 数据域减33报文 68 03 00 00 00 00 00 68 81 06 20 91 29 00 00 00 xx 16 33 34 TX://读正向有功尖峰 35 发送的原始报文 68 03 00 00 00 00 00 68 01 02 44 C3 DD 16 36 数据域减33报文 68 03 00 00 00 00 00 68 01 02 11 90 xx 16 37 38 RX://00 00 00 00 = 0 = 0 kWh 39 接收的原始报文 68 03 00 00 00 00 00 68 81 06 44 C3 33 33 33 33 2D 16 40 数据域减33报文 68 03 00 00 00 00 00 68 81 06 11 90 00 00 00 00 xx 16 41 42 TX://读正向有功峰 43 发送的原始报文 68 03 00 00 00 00 00 68 01 02 45 C3 DE 16 44 数据域减33报文 68 03 00 00 00 00 00 68 01 02 12 90 xx 16 45 RX://43 00 00 00 = 43 = 0.43 kWh 46 接收的原始报文 68 03 00 00 00 00 00 68 81 06 45 C3 76 33 33 33 71 16 47 数据域减33报文 68 03 00 00 00 00 00 68 81 06 12 90 43 00 00 00 xx 16 48 49 TX://读正向有功平 50 发送的原始报文 68 03 00 00 00 00 00 68 01 02 46 C3 DF 16 51 数据域减33报文 68 03 00 00 00 00 00 68 01 02 13 90 xx 16 52 RX://84 00 00 00 = 84 = 0.84 kWh 53 接收的原始报文 68 03 00 00 00 00 00 68 81 06 46 C3 B7 33 33 33 B3 16 54 数据域减33报文 68 03 00 00 00 00 00 68 81 06 13 90 84 00 00 00 xx 16 55 56 TX://正向有功谷 57 发送的原始报文 68 03 00 00 00 00 00 68 01 02 47 C3 E0 16 58 数据域减33报文 68 03 00 00 00 00 00 68 01 02 14 90 xx 16 59 RX://00 00 00 00 = 0 = 0 kWh 60 接收的原始报文 68 03 00 00 00 00 00 68 81 06 47 C3 33 33 33 33 30 16 61 数据域减33报文 68 03 00 00 00 00 00 68 81 06 14 90 00 00 00 00 xx 16 62 63 TX://读A相电压 64 发送的原始报文 68 03 00 00 00 00 00 68 01 02 44 E9 03 16 65 数据域减33报文 68 03 00 00 00 00 00 68 01 02 11 B6 xx 16 66 RX://00 01 = 100 = 100 V 67 接收的原始报文 68 03 00 00 00 00 00 68 81 04 44 E9 33 34 EC 16 68 数据域减33报文 68 03 00 00 00 00 00 68 81 04 11 B6 00 01 xx 16 69 70 TX://B相电压 71 发送的原始报文 68 03 00 00 00 00 00 68 01 02 45 E9 04 16 72 数据域减33报文 68 03 00 00 00 00 00 68 01 02 12 B6 xx 16 73 RX://00 00 = 0 = 0 V 74 接收的原始报文 68 03 00 00 00 00 00 68 81 04 45 E9 33 33 EC 16 75 数据域减33报文 68 03 00 00 00 00 00 68 81 04 12 B6 00 00 xx 16 76 77 TX://C相电压 78 发送的原始报文 68 03 00 00 00 00 00 68 01 02 46 E9 05 16 79 数据域减33报文 68 03 00 00 00 00 00 68 01 02 13 B6 xx 16 80 RX://00 01 = 100 = 100 V 81 接收的原始报文 68 03 00 00 00 00 00 68 81 04 46 E9 33 34 EE 16 82 数据域减33报文 68 03 00 00 00 00 00 68 81 04 13 B6 00 01 xx 16 83 84 TX://A相电流 85 发送的原始报文 68 03 00 00 00 00 00 68 01 02 54 E9 13 16 86 数据域减33报文 68 03 00 00 00 00 00 68 01 02 21 B6 xx 16 87 RX://99 04 = 499 = 4.99 A 88 接收的原始报文 68 03 00 00 00 00 00 68 81 04 54 E9 CC 37 98 16 89 数据域减33报文 68 03 00 00 00 00 00 68 81 04 21 B6 99 04 xx 16 90 91 TX://B相电流 92 发送的原始报文 68 03 00 00 00 00 00 68 01 02 55 E9 14 16 93 数据域减33报文 68 03 00 00 00 00 00 68 01 02 22 B6 xx 16 94 RX://00 00 = 0 = 0 A 95 接收的原始报文 68 03 00 00 00 00 00 68 81 04 55 E9 33 33 FC 16 96 数据域减33报文 68 03 00 00 00 00 00 68 81 04 22 B6 00 00 xx 16 97 98 TX://C相电流 99 发送的原始报文 68 03 00 00 00 00 00 68 01 02 56 E9 15 16 100 数据域减33报文 68 03 00 00 00 00 00 68 01 02 23 B6 xx 16 101 RX://99 04 = 499 = 4.99 A 102 接收的原始报文 68 03 00 00 00 00 00 68 81 04 56 E9 CC 37 9A 16 103 数据域减33报文 68 03 00 00 00 00 00 68 81 04 23 B6 99 04 xx 16 104 105 TX://读瞬时有功功率,电度表不上传符号位,只上传幅值 106 发送的原始报文 68 03 00 00 00 00 00 68 01 02 63 E9 22 16 107 数据域减33报文 68 03 00 00 00 00 00 68 01 02 30 B6 xx 16 108 RX://61 86 00 = 8661 = 0.8661 kW 109 110 接收的原始报文 68 03 00 00 00 00 00 68 81 05 63 E9 94 B9 33 25 16 111 数据域减33报文 68 03 00 00 00 00 00 68 81 05 30 B6 61 86 00 xx 16 112 113 TX://读瞬时无功功率 114 发送的原始报文 68 03 00 00 00 00 00 68 01 02 73 E9 32 16 115 数据域减33报文 68 03 00 00 00 00 00 68 01 02 40 B6 xx 16 116 RX://00 00 = 0 = 0 kvar 117 接收的原始报文 68 03 00 00 00 00 00 68 81 04 73 E9 33 33 1A 16 118 数据域减33报文 68 03 00 00 00 00 00 68 81 04 40 B6 00 00 xx 16 119 120 TX://读总功率因数 121 发送的原始报文 68 03 00 00 00 00 00 68 01 02 83 E9 42 16 122 数据域减33报文 68 03 00 00 00 00 00 68 01 02 50 B6 xx 16 123 RX://99 09 = 999 = 0.999 [功率因数没有单位] 124 接收的原始报文 68 03 00 00 00 00 00 68 81 04 83 E9 CC 3C CC 16 125 数据域减33报文 68 03 00 00 00 00 00 68 81 04 50 B6 99 09 xx 16 126 127 //END
使用虚拟串口软件http://www.cr173.com/soft/21406.html和虚拟电表软件http://download.csdn.net/detail/wanun55/3830244#comment
可以在VC上开发模拟抄读电表的通信协议程序,注意虚拟电表软件的645国网协议(97版本)帧格式存在一定的错误,抄读正向有功电能数据域多了一个字节