1、项目的需求来源
做为程序员码代码,开发软件,开发通信协议,相信避免不了与各种校验码打交道。校验码是通信协议中数据接收方用来校验数据合法性的一个编码。那么举个栗子,请看今早我和隔壁老王的对话,
隔壁老王:今天你吃了么?“嘎哈”
老赵:我今天吃的是包子。“哇哈”
哎,有人会问了,你和老王说话怎么还带着口头禅,这个就是为了向你说明校验码,老王说话后面的加引号的“嘎哈”,我听到后,知道老王是东北的,说话带个“嘎哈”就验证了这是老王说的,老王听到我说话后面加引号的“哇哈”,知道了这是老赵说话的校验码,这话是老赵说的。
根据实际的MODBUS RTU通信协议就能更好的理解出来通信协议中校验码的功能:
地址 | 功能代码 | 数据数量 | 数据1 | ... | 数据n | CRC低字节 | CRC高字节 |
这个协议中的最后两个字节,CRC低字节,CRC高字节就是校验码了,这个校验码是前面所有数据、数据数量、功能代码、地址这些字节经过特殊的运算得出来。
开发过程中今天MODBUS协议,明天ISO14443A,后天TCP/IP协议...,测试过程中要编写各种协议,计算各种校验码,我想你最开始是手算或都上网找一个工具来计算,今天咱们就写一个单机板的计算工具,具有计算异或和,累加和,MODBUS CRC,ISO14443 CRC计算功能的校验码计算工具。先上开发工作成果,哈哈
2、各种校验码的计算算法
(1)NOR 异或和计算
nor = d1 ^ d2 ^ ... dn d1 - dn是要参与计算的数据,是一个单字节的数据,这个异或计算就是对所有数据进行逻辑按位异或
(2)SUM累加和计算
sum = d1 + d2 + d3...dn d1 - dn是要参与计算的数据,是一个单字节的数据,这个累加和计算就是对所有数据进行累加,累加的结果还是取单字节,不考虑溢出的高位部分
(3)ISO14443的CRC计算
G(x)=x^16 + x ^12 + x^5 + 1,这个算法复杂一点,具体可以参考ISO14443协议中的第60页,ISO14443协议中有CRC A, CRC B的计算法,这个两种校验码的算法一样,初始值不一样。
(4)MODBUS CRC计算
这个CRC计算也复杂一点,具体可以参考MODBUS协议的第72页,为了加快计算速度,采用查表的方法来计算CRC值。
3、设计UI界面
打开QtCreat新建一个UI文件,按照你想的样子添加控件,我的设计是这个样子的。
窗口分成上下两个输入输出文本框,上面的用于输入要计算的数据,下面的用于输出计算结果,中间有放有单选按钮,用于选择计算的算法,最下面还有两个单选按钮“结果取反”和“结果不取反”这个选项只对计算NOR时有效,对NOR计算的结果增加一次取反操作,其他算法计算时无效。界面编辑完成后,保存,工程目录下面会生成一个mainwindow.ui的文件,在命令行中运行pyuic5 mainwindow.ui -o mainwindow.py,把ui文件生成了py的代码文件。
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.8.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.setEnabled(True)
MainWindow.resize(472, 349)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QtCore.QSize(450, 349))
MainWindow.setMaximumSize(QtCore.QSize(500, 349))
self.centralWidget = QtWidgets.QWidget(MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralWidget.sizePolicy().hasHeightForWidth())
self.centralWidget.setSizePolicy(sizePolicy)
self.centralWidget.setObjectName("centralWidget")
self.label = QtWidgets.QLabel(self.centralWidget)
self.label.setGeometry(QtCore.QRect(10, 0, 91, 16))
self.label.setObjectName("label")
self.textEdit = QtWidgets.QTextEdit(self.centralWidget)
self.textEdit.setGeometry(QtCore.QRect(10, 20, 441, 101))
self.textEdit.setObjectName("textEdit")
self.label_2 = QtWidgets.QLabel(self.centralWidget)
self.label_2.setGeometry(QtCore.QRect(10, 130, 81, 16))
self.label_2.setObjectName("label_2")
self.textEdit_2 = QtWidgets.QTextEdit(self.centralWidget)
self.textEdit_2.setGeometry(QtCore.QRect(10, 150, 451, 101))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.textEdit_2.sizePolicy().hasHeightForWidth())
self.textEdit_2.setSizePolicy(sizePolicy)
self.textEdit_2.setObjectName("textEdit_2")
self.pushButton = QtWidgets.QPushButton(self.centralWidget)
self.pushButton.setGeometry(QtCore.QRect(380, 260, 75, 23))
self.pushButton.setStyleSheet("color: rgb(85, 0, 255);\n"
"font: 75 9pt \"Arial\";")
self.pushButton.setObjectName("pushButton")
self.radioButton = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton.setGeometry(QtCore.QRect(10, 260, 89, 16))
self.radioButton.setChecked(True)
self.radioButton.setObjectName("radioButton")
self.radioButton_2 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_2.setGeometry(QtCore.QRect(120, 260, 89, 16))
self.radioButton_2.setObjectName("radioButton_2")
self.radioButton_3 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_3.setGeometry(QtCore.QRect(90, 130, 71, 16))
self.radioButton_3.setObjectName("radioButton_3")
self.radioButton_4 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_4.setGeometry(QtCore.QRect(160, 130, 71, 16))
self.radioButton_4.setObjectName("radioButton_4")
self.radioButton_5 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_5.setGeometry(QtCore.QRect(230, 130, 81, 16))
self.radioButton_5.setObjectName("radioButton_5")
self.radioButton_6 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_6.setGeometry(QtCore.QRect(310, 130, 81, 16))
self.radioButton_6.setObjectName("radioButton_6")
self.radioButton_7 = QtWidgets.QRadioButton(self.centralWidget)
self.radioButton_7.setGeometry(QtCore.QRect(390, 130, 89, 16))
self.radioButton_7.setObjectName("radioButton_7")
#创建一个buttongroup进行分组
self.computButtonGroup = QtWidgets.QButtonGroup(self.centralWidget)
self.computButtonGroup.addButton(self.radioButton_3)
self.computButtonGroup.addButton(self.radioButton_4)
self.computButtonGroup.addButton(self.radioButton_5)
self.computButtonGroup.addButton(self.radioButton_6)
self.computButtonGroup.addButton(self.radioButton_7)
self.radioButton_3.setChecked(True)
MainWindow.setCentralWidget(self.centralWidget)
self.menuBar = QtWidgets.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 472, 23))
self.menuBar.setObjectName("menuBar")
self.menu = QtWidgets.QMenu(self.menuBar)
self.menu.setObjectName("menu")
MainWindow.setMenuBar(self.menuBar)
self.statusBar = QtWidgets.QStatusBar(MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.actionud = QtWidgets.QAction(MainWindow)
self.actionud.setObjectName("actionud")
self.actionvec = QtWidgets.QAction(MainWindow)
self.actionvec.setObjectName("actionvec")
self.menu.addAction(self.actionud)
self.menu.addAction(self.actionvec)
self.menuBar.addAction(self.menu.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Nor computer"))
self.label.setText(_translate("MainWindow", "输入数据(HEX)"))
self.textEdit.setHtml(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">请输入十六进制数据,以空格分隔</p></body></html>"))
#设置默认选中输入框中的文字 zhaoshimin 20180829
self.textEdit.selectAll()
self.label_2.setText(_translate("MainWindow", "计算结果(HEX)"))
self.pushButton.setText(_translate("MainWindow", "计算"))
self.radioButton.setText(_translate("MainWindow", "结果取反"))
self.radioButton_2.setText(_translate("MainWindow", "结果不取反"))
self.radioButton_3.setText(_translate("MainWindow", "NOR计算"))
self.radioButton_4.setText(_translate("MainWindow", "SUM计算"))
self.radioButton_5.setText(_translate("MainWindow", "CRC-A计算"))
self.radioButton_6.setText(_translate("MainWindow", "CRC-B计算"))
self.radioButton_7.setText(_translate("MainWindow", "ModbusCRC"))
self.menu.setTitle(_translate("MainWindow", "帮助"))
self.actionud.setText(_translate("MainWindow", "关于"))
self.actionvec.setText(_translate("MainWindow", "退出"))
4、编写各个算法的计算算法
另外创建一个Nor_com.py的文件,这个文件用于实现计算算法,算法和界面文件分开,减少业务逻辑和界面的耦合性。
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtGui import QIcon
from mainwindow import Ui_MainWindow
import sys
from picon import *
aucCRCHi = [
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40]
aucCRCLo = [
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40]
#modbus crc计算函数
def modbus_crc(data):
CRCHi = 0xFF
CRCLo = 0xFF
index = 0
for i in range(0, len(data)):
index = CRCLo ^ data[i]
CRCLo = CRCHi ^ aucCRCHi[index]
CRCHi = aucCRCLo[index]
return ((CRCHi << 8 )| CRCLo)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
# 设置应用程序的窗口图标
self.setWindowIcon(QIcon(":/NOR_computer.png"))
#默认选择结果取反
self.radioButton.setChecked(True)
self.pushButton.clicked.connect(self.nor_com)
self.actionud.triggered.connect(self.about) #菜单的点击事件是triggered
self.actionvec.triggered.connect(self.close)
def about(self):
print('this is about')
#弹出关于对话框
QMessageBox.about(self,("关于"),("""Nor Computer V1.0\nSupport: fhqlongteng@163.com\nCopyright: 1982-2017"""))
QMessageBox.StandardButtons(QMessageBox.Ok)
def nor_com(self):
self.statusBar.showMessage('开始计算')
#从输入框读入字符串
input_s = self.textEdit.toPlainText()
#删除前面和后面的空格
input_s = input_s.strip()
out_s = input_s
nor = 0
sum = 0
crc_type = 0x6363
modbus_data = []
if self.radioButton_5.isChecked():
#ISO14443A CRC A的计算初值
crc_type = 0x6363
elif self.radioButton_6.isChecked():
#ISO14443A CRC B的计算初值
crc_type = 0xFFFF
while input_s !='':
try:
num = int(input_s[0:2],16)
except:
print(sys.exc_info()[0])
self.statusBar.showMessage('计算中:ValueError')
#弹出警告对话框
QMessageBox.warning(self,("警告"),("""请输入十六进制字节数据,以空格隔开"""))
QMessageBox.StandardButtons(QMessageBox.Ok)
return None
#判断进行nor运算还是sum运算
if self.radioButton_3.isChecked():
#异或和计算
nor = nor ^ num
elif self.radioButton_4.isChecked():
#SUN累加和运算
nor = nor + num
elif self.radioButton_5.isChecked() or self.radioButton_6.isChecked():
#ISO14443A CRC运算
sum = num ^ (crc_type & 0xFF)
sum = (sum ^ (sum << 4)) & 0xFF
crc_type = (crc_type >> 8) ^ ((sum << 8) & 0xffff) ^ ((sum << 3) & 0xffff) ^ ((sum >> 4) & 0xffff)
crc_type = crc_type & 0xFFFF
elif self.radioButton_7.isChecked():
#把数据放入计算列表中去
modbus_data.append(num)
input_s = input_s[2:]
input_s = input_s.strip()
#NOR SUM的计算结果取反操作
if self.radioButton.isChecked():
nor = ~nor
else:
pass
nor = nor & 0xFF
#print('%02x'%(nor))
#状态栏显示和输出计算结果
if self.radioButton_4.isChecked():
out_s = out_s + ' ' + '{:02x}'.format(nor)
self.statusBar.showMessage('计算完成 SUM=0x' + '{:x}'.format(nor).upper() + '(' +'{:d}'.format(nor)+')')
elif self.radioButton_3.isChecked():
out_s = out_s + ' ' + '{:02x}'.format(nor)
self.statusBar.showMessage('计算完成 NOR=0x' + '{:x}'.format(nor).upper() + '(' +'{:d}'.format(nor)+')')
elif self.radioButton_5.isChecked():
#CRC A计算结果
out_s = out_s + ' ' + '{:02x}'.format(crc_type & 0xFF) + ' ' + '{:02x}'.format(crc_type >> 8)
self.statusBar.showMessage('计算完成 ISO14443A CRCA=0x' + '{:x}'.format(crc_type).upper() + '(' +'{:d}'.format(crc_type)+')')
elif self.radioButton_6.isChecked():
#CRC B计算结果
crc_type = (~crc_type) & 0xffff
out_s = out_s + ' ' + '{:02x}'.format(crc_type >> 8) + ' ' + '{:02x}'.format(crc_type & 0xFF)
self.statusBar.showMessage('计算完成 ISO14443B CRCB=0x' + '{:x}'.format(crc_type).upper() + '(' +'{:d}'.format(crc_type)+')')
elif self.radioButton_7.isChecked():
#MODBUS crc计算结果
crc = modbus_crc(modbus_data)
out_s = out_s + ' ' + '{:02x}'.format(crc & 0xFF) + ' ' + '{:02x}'.format((crc >> 8) & 0xFF)
self.statusBar.showMessage('计算完成 Modbus CRC_L=0x' + '{:02X}'.format(crc & 0xFF).upper() + ' CRC_H=0x' +'{:02X}'.format(crc >> 8))
#转换成大写字母输出
self.textEdit_2.setText(out_s.upper())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
5、设计软件的图标
软件的图标设计一个ico类型的文件和png类型的文件,两个图标一样,不同的文件格式而已,命名为Nor_commputer.ico, Nor_commputer.png,ico文件用于打包成EXE程序里的软件图标,png文件用于在程序窗口的左上角显示出来一个图标。 编辑一个资源文件nor_com.qrc,写入软件中要调用的图片资源Nor_commputer.png。最后命令行中输入pyrcc5 nor_com.qrc -o pyico.py生成程序内部显示图标使用的图标文件。
6、打包生成可执行文件
使用pyinstaller打包,命令行中输入 pyinstaller --icnotallow=Nor_com.ico --paths=E:\python_test\api-ms-win -F -w Nor_com.py运行,具体使用方法可以参考我的博文python使用pyinstaller打包生成EXE可执行文件。