在数据通信和存储领域,错误检测是保证数据完整性的重要环节。CRC(循环冗余校验)作为一种高效的错误检测算法,被广泛应用于各种通信协议和存储介质中。在最近的工作过程中,刚好被接收到了编写一个CRC校验工具的任务,那么刚好发表一下如何使用 Qt 框架实现一个功能完善的 CRC 计算工具这篇文章,因为时间有限所以只编写了16进制的所有计算方法。
CRC 算法原理概述
CRC(Cyclic Redundancy Check)循环冗余校验是一种通过数学运算来检测数据传输或存储中错误的方法。其核心原理是:
- 选择一个生成多项式 G (x)(对应代码中的 poly 参数)
- 将原始数据视为一个二进制数 D (x)
- 通过模 2 除法计算 D (x) * x^n / G (x) 的余数 R (x)(n 为多项式的次数)
- 将余数 R (x) 附加到原始数据后发送
- 接收端用同样的多项式进行校验,若余数为 0 则认为数据正确
CRC 算法的关键参数包括:
- 宽度 (Width):校验码的位数,常见的有 8 位、16 位、32 位等
- 多项式 (Poly):生成多项式,决定了校验的特性
- 初始值 (Init):CRC 计算的初始值
- 异或输出 (XorOut):计算完成后与结果异或的值
- 输入反转 (RefIn):是否对输入数据进行位反转
- 输出反转 (RefOut):是否对输出结果进行位反转
CRC 计算工具实现
下面介绍的 Qt 应用程序实现了一个功能完整的 CRC 计算工具,支持多种标准 CRC 算法和灵活的参数配置。
整体架构设计
该程序采用 Model-View 架构,主要包含以下几个部分:
- 界面层:使用 Qt 的 UI 设计器创建交互界面
- 模型层:定义 CRC 算法的参数模型
- 控制层:处理用户输入和算法调用
.cpp核心代码解析
首先看一下 CRC 模型的定义,程序支持多种标准 CRC 算法:
#include "crcwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QMessageBox>
#include <QRegularExpression>
#include "ui_crcwidget.h"
#include <QDebug>
#include<QString>
#include <QRegularExpression>
enum InputMode {//设置16进制和ASCII两种方式的输入
HexMode,
AsciiMode
};
struct CRCModel {
QString name; // 算法名称
int width; // 校验码宽度
uint16_t poly; // 生成多项式
uint16_t init; // 初始值
uint16_t xorOut; // 异或输出值
bool refIn; // 输入是否反转
bool refOut; // 输出是否反转
};
const QVector<CRCModel> presetModels = {// 预设的标准CRC模型
{"CRC-16/CCITT",16, 0x1021, 0x0000, 0x0000, true, true},
{"CRC-16/IBM", 16,0x8005, 0x0000, 0x0000, true, true},
{"CRC-16/MODBUS", 16,0x8005, 0xFFFF, 0x0000, true, true},
{"CRC-16/MAXIM", 16,0x8005, 0x0000, 0xFFFF, true, true},
{"CRC-16/CCITT-FALSE", 16,0x1021, 0xFFFF, 0x0000, false, false},
{"CRC-16/X25", 16,0x1021, 0xFFFF, 0xFFFF, true, true},
{"CRC-16/XMODEM", 16,0x1021, 0x0000, 0x0000, false, false},
{"CRC-16/DNP", 16,0x3D65, 0x0000, 0xFFFF, true, true},
{"CRC-16/USB", 16,0x8005, 0xFFFF, 0xFFFF, true, true},
};
界面初始化和事件连接部分代码:
crcwidget::crcwidget(QWidget *parent)
:QMainWindow(parent),
ui(new Ui::crcwidget)
{
ui->setupUi(this);
setWindowTitle("CRC循环冗余校验");
ui->polyLineEdit->setText("1021");
ui->widthLineEdit->setText("16");
ui->initLineEdit->setText("FFFF");
ui->xorLineEdit->setText("0000");
setupPresetModels();
// 模式选择
ui->modeComboBox->addItem("十六进制模式", HexMode);
ui->modeComboBox->addItem("ASCII模式", AsciiMode);
ui->modeComboBox->setCurrentIndex(0);
connect(ui->crcTypeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &crcwidget::updateParams);
connect(ui->calcButton, &QPushButton::clicked, this, &crcwidget::calculateCRC);
connect(ui->clearButton, &QPushButton::clicked, this, &crcwidget::clearAll);
}
crcwidget::~crcwidget()
{
delete ui;
}
CRC 计算的核心算法实现,这里使用了直接计算的方式:
uint16_t reverseBits16(uint16_t value) {
uint16_t result = 0;
for (int i = 0; i < 16; i++) {
result = (result << 1) | (value & 1);
value >>= 1;
}
return result;
}
uint32_t crcwidget::calculateCRC16(uint8_t *data, uint32_t len, uint16_t poly,int width,
uint16_t init, uint16_t xorOut, bool refIn, bool refOut)
{
qDebug()<<refIn<<refOut;
qDebug()<<"="<<width;
//qDebug()<<poly<<" "<<init<<" "<<xorOut;
poly= reverseBits16(poly);
uint16_t crc = init;
for(uint32_t i = 0; i < len; ++i) {
if(!refIn)
data[i]=reverseByte(data[i]);
crc ^= static_cast<uint16_t>(data[i]);
for(int j = 0; j < 8; ++j) {
if(crc & 0x0001) {
crc = (crc >> 1) ^ poly;
} else {
crc >>= 1;
}
}
}
// crc=(crc ^ xorOut);
qDebug()<<crc;
if(!refOut) {
crc = (reverseByte(crc >> 8) | reverseByte(crc & 0xFF)<< 8 );
//crc= reverseByte(crc & 0xFFFF);
}
return crc ^ xorOut;
}
uint8_t crcwidget::reverseByte(uint8_t b)
{
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
void crcwidget::clearAll()
{
ui->inputTextEdit->clear();
ui->resultHexLineEdit->clear();
ui->resultBinLineEdit->clear();
}
输入处理与模式切换
程序支持两种输入模式:十六进制模式和 ASCII 模式,输入处理代码如下:
void crcwidget::setupPresetModels() {
for(const auto &model : presetModels) {
ui->crcTypeComboBox->addItem(model.name);
}
updateParams(0); // 默认选择第一个模型
}
void crcwidget::updateParams(int index) {
if(index >= 0 && index < presetModels.size()) {
const CRCModel &model = presetModels[index];
ui->polyLineEdit->setText(QString::number(model.poly, 16).toUpper());
ui->widthLineEdit->setText(QString::number(model.width, 10).toUpper());
ui->initLineEdit->setText(QString::number(model.init, 16).toUpper());
ui->xorLineEdit->setText(QString::number(model.xorOut, 16).toUpper());
ui->refInCheckBox->setChecked(model.refIn);
ui->refOutCheckBox->setChecked(model.refOut);
}
}
void crcwidget::updateMode(int index)
{
Q_UNUSED(index);
clearAll();
InputMode mode = static_cast<InputMode>(ui->modeComboBox->currentData().toInt());
if (mode == AsciiMode) {
ui->inputTextEdit->setPlaceholderText("请输入ASCII字符串");
} else {
ui->inputTextEdit->setPlaceholderText("请输入16进制数据,如: 01 02 03 AB CD");
}
}
void crcwidget::calculateCRC()
{
QString input = ui->inputTextEdit->toPlainText();
if(input.isEmpty()) {
QMessageBox::warning(this, "错误", "请输入有效数据");
return;
}
InputMode mode = static_cast<InputMode>(ui->modeComboBox->currentData().toInt());
QByteArray data;
if (mode == HexMode) {//选择16进制运算
// 格式化处理16进制
QString formattedInput;
QRegularExpression standardFormat("^(\\d{2}( \\d{2})*)?$");
QRegularExpression hexFormat("^([0-9A-Fa-f]{2}( [0-9A-Fa-f]{2})*)?$");
if(standardFormat.match(input).hasMatch() || hexFormat.match(input).hasMatch()) {
formattedInput = input;
} else {
// 移除非数字和字母字符
QString cleanInput = input;
cleanInput.remove(QRegularExpression("[^0-9A-Fa-f]"));
// 处理纯数字情况
if(QRegularExpression("^\\d+$").match(cleanInput).hasMatch()) {
// 确保每个数字都是两位数
for(int i = 0; i < cleanInput.length(); i++) {
if(!formattedInput.isEmpty()) formattedInput += " ";
formattedInput += "0" + cleanInput.mid(i, 1);
}
} else {
// 处理16进制混合情况
if(cleanInput.length() % 2 != 0) {
cleanInput.prepend('0');
}
for(int i = 0; i < cleanInput.length(); i += 2) {
if(!formattedInput.isEmpty()) formattedInput += " ";
formattedInput += cleanInput.mid(i, 2).toUpper();
}
}
// 更新显示
ui->inputTextEdit->setPlainText(formattedInput);
}
// 验证16进制输入
QRegularExpression hexMatcher("^([0-9A-Fa-f]{2})( [0-9A-Fa-f]{2})*$");
if(!hexMatcher.match(formattedInput).hasMatch()) {
QMessageBox::warning(this, "错误", "请输入有效的16进制数据");
return;
}
data = QByteArray::fromHex(formattedInput.remove(' ').toLatin1());
}
else { //选择ASCII运算
// 检查输入是否已经是格式化后的十六进制形式
QRegularExpression hexFormat("^([0-9A-Fa-f]{2}( [0-9A-Fa-f]{2})*)?$");
bool isHexFormatted = hexFormat.match(input).hasMatch();
if (isHexFormatted) {
// 如果已经是十六进制格式,直接转换为字节数组
data = QByteArray::fromHex(input.remove(' ').toLatin1());
} else {
// 否则使用字符串的UTF-8编码
data = input.toUtf8();
// 显示格式化后的输入
QString displayText;
for (char c : data) {
if (!displayText.isEmpty()) displayText += " ";
displayText += QString("%1").arg(static_cast<uchar>(c), 2, 16, QLatin1Char('0')).toUpper();
}
ui->inputTextEdit->setPlainText(displayText);
}
}
// 获取CRC参数
bool ok;
uint16_t poly = ui->polyLineEdit->toPlainText().toUShort(&ok, 16);
int width = ui->widthLineEdit->toPlainText().toUShort(&ok, 16);
uint16_t init = ui->initLineEdit->toPlainText().toUShort(&ok, 16);
uint16_t xorOut = ui->xorLineEdit->toPlainText().toUShort(&ok, 16);
bool refIn = ui->refInCheckBox->isChecked();
bool refOut = ui->refOutCheckBox->isChecked();
// // 转换为字节数组
// QByteArray data = QByteArray::fromHex(formattedInput.remove(' ').toLatin1());
// 计算CRC
uint16_t crc = calculateCRC16(reinterpret_cast<uint8_t*>(data.data()),
data.size(),poly,width, init, xorOut, refIn, refOut);
// 显示结果
ui->resultHexLineEdit->setText(QString("%1").arg(crc, 4, 16, QLatin1Char('0')).toUpper());
ui->resultBinLineEdit->setText(QString::number(crc, 2).rightJustified(16, '0'));
}
界面设计与用户交互
该 CRC 计算工具的界面设计遵循直观易用的原则,主要包含以下几个部分:
- 算法选择区:通过下拉框选择标准 CRC 算法,如 CRC-16/CCITT、CRC-16/MODBUS 等
- 参数配置区:显示和配置 CRC 算法的各项参数,包括多项式、初始值等
- 输入区:支持十六进制和 ASCII 两种模式的输入,并自动进行格式校验
- 结果显示区:以十六进制和二进制两种形式显示计算得到的 CRC 值
- 操作按钮:包括计算按钮和清除按钮,方便用户操作
功能拓展建议
在该现有程序的基础上面,还可以添加许多别的功能:
- 32 位 CRC 算法支持:如 CRC-32/ISO、CRC-32/MPEG-2 等
- 文件 CRC 计算:支持直接计算整个文件的 CRC 值
- 自定义多项式:允许用户输入任意多项式进行计算
- 历史记录功能:保存之前的计算结果,方便对比
不过博主个人比较懒,加上目前的工作对别的功能无太大需求,所以就没有写。

.h文件代码
此外,上述不过是该程序的所有.cpp代码,一下为该程序的.h部分代码:
#ifndef CRCWIDGET_H
#define CRCWIDGET_H
#include <QMainWindow>
#include <cstdint>
QT_BEGIN_NAMESPACE
namespace Ui {
class crcwidget;
}
QT_END_NAMESPACE
class crcwidget : public QMainWindow
{
Q_OBJECT
public:
explicit crcwidget(QWidget *parent = nullptr);
~crcwidget();
private slots:
void calculateCRC();
void clearAll();
void updateParams(int index); // 模型选择更新槽函数
void updateMode(int index); // 模式选择更新槽函数
private:
uint32_t calculateCRC16(uint8_t *data, uint32_t len, uint16_t poly,int width,
uint16_t init, uint16_t xorOut, bool refIn, bool refOut);
uint8_t reverseByte(uint8_t byte);
void setupPresetModels(); // 预设模型初始化
Ui::crcwidget *ui;
};
#endif // CRCWIDGET_H
运行结果
这就是该程序运行的UI界面
这就是该文章的全部内容了,博主又要去愉快的工作(坚强)。


2361

被折叠的 条评论
为什么被折叠?



