April 20, 2023 by Ivan Solovev | Comments
2023年4月20日:Ivan Solovev |评论
The latest Qt 6.5 release introduced a lot of new features. You can read about all of them in the recent blog post. This blog post will give an overview of the improvements that we have made to the Qt CAN Bus module.
最新发布的Qt 6.5引入了许多新功能。可以在最近的博客文章中阅读到所有这些内容。这篇博客文章将概述我们对Qt CAN总线模块所做的改进。
The Qt CAN Bus module has always provided APIs for high-level manipulation with CAN bus:
Qt CAN总线模块始终为CAN总线的高级操作提供API:
The CAN bus frame consists of a FrameId and a Payload. In the existing API, the FrameId is represented as an unsigned integer, and the payload is just a QByteArray holding some raw bytes.
CAN总线框架由一个FrameId和一个Payload组成。在现有的API中,FrameId表示为一个无符号整数,有效载荷只是一个保存一些原始字节的QByteArray
In practice, higher-level protocols are applied on top of CAN bus frames. This overcomes the generic definition of arbitrary payloads and FrameIds, which replaces it with unique identifiers for bus devices, message and signal types which may occur, and provides type information for the values within signals.
在实践中,更高级别的协议应用于CAN总线框架之上。这克服了任意有效载荷和FrameId的通用定义,后者将其替换为总线设备、可能出现的消息和信号类型的唯一标识符,并为信号内的值提供类型信息。
Before Qt 6.5, the users had to provide their own implementation for extracting these values. With Qt 6.5, we introduced a set of APIs to simplify this process.
在Qt 6.5之前,用户必须提供自己的实现来提取这些值。在Qt 6.5中,我们引入了一组API来简化这个过程。
The new APIs provide a way to describe the top-level protocol. Later on, these rules can be used to decode an incoming CAN frame, as well as to encode data into a CAN frame before sending it to a device.
新的API提供了一种描述顶级协议的方法。稍后,这些规则可以用于解码传入的can帧,以及在将数据发送到设备之前将数据编码到can帧中。
Let’s have a closer look at the APIs:
让我们仔细看看API:
The QCanFrameProcessor class uses the descriptions provided by other new classes. It provides two main methods for encoding and decoding the frames:
QCanFrameProcessor类使用其他新类提供的描述。它提供了对帧进行编码和解码的两种主要方法:
Let’s develop a small example to demonstrate the new APIs in action.
让我们开发一个小示例来演示新的API在实际中的作用。
Let’s consider a protocol with the following format:
让我们考虑一个具有以下格式的协议:
This format can be visualized with the following image.
这种格式可以通过下面的图像进行可视化。
Let’s see how we can describe this protocol in terms of the new API.
让我们看看如何用新的API来描述这个协议。
Unique Identifier
唯一标识符
Let’s start with the unique identifier.
让我们从唯一标识符开始。
QCanUniqueIdDescription uid;
uid.setSource(QtCanBus::DataSource::FrameId);
uid.setEndian(QSysInfo::Endian::LittleEndian);
uid.setStartBit(0);
uid.setBitLength(11);
We define the source (FrameId), the endian (Little-Endian), the start bit and the bit length of the unique identifier.
我们定义了唯一标识符的源(FrameId)、端(Little endian)、起始位和位长度。
The source defines a part of the CAN frame which will be used to extract the value. It can be either a FrameId, or a Payload.
源定义了CAN帧的一部分,该部分将用于提取值。它可以是FrameId,也可以是Payload。
Signal and Message Descriptions
信号和消息描述
Next, let’s define the signal descriptions.
接下来,让我们定义信号描述。
QCanSignalDescription s0;
s0.setName(u"signal 0"_s);
s0.setDataSource(QtCanBus::DataSource::Payload);
s0.setDataEndian(QSysInfo::Endian::LittleEndian);
s0.setDataFormat(QtCanBus::DataFormat::UnsignedInteger);
s0.setStartBit(0);
s0.setBitLength(16);
QCanSignalDescription s1;
s1.setName(u"signal 1"_s);
s1.setDataSource(QtCanBus::DataSource::Payload);
s1.setDataEndian(QSysInfo::Endian::LittleEndian);
s1.setDataFormat(QtCanBus::DataFormat::SignedInteger);
s1.setStartBit(16);
s1.setBitLength(32);
For both signals, we define the signal name, the source (Payload), the endian (Little-Endian), the data type, the start bit, and the bit length. Note that the bit numbering is continuous for the whole payload. Bit 0 represents the first bit of Byte 0, and Bit 63 represents the last bit of Byte 7. The signal name must be unique within a message description. The signal names are used to provide meaningful results when parsing the frame, and also to identify the proper rules for encoding the values into a newly generated frame. The QCanSignalDescription class allows you to specify more parameters. Please refer to the documentation for the full list.
对于这两个信号,我们定义了信号名称、源(Payload)、端序(Little endian)、数据类型、起始位和位长度。注意,比特编号对于整个有效载荷是连续的。比特0表示字节0的第一个比特,比特63表示字节7的最后一个比特。信号名称在消息描述中必须是唯一的。信号名称用于在解析帧时提供有意义的结果,还用于确定将值编码到新生成的帧中的正确规则。QCanSignalDescription类允许您指定更多参数。请参阅文档以获取完整列表
Once the signal description is specified, we can define the message description.
一旦指定了信号描述,我们就可以定义消息描述。
QCanMessageDescription msg;
msg.setName(u"example message"_s);
msg.setSize(8);
msg.setUniqueId(QtCanBus::UniqueId{0x123});
msg.setSignalDescriptions({s0, s1});
For the message description, we specify the payload size, the list of signal descriptions contained in this message, and a unique identifier. The unique identifier will be used to select a proper message description when decoding the incoming CAN frame. Like with signal descriptions, the QCanMessageDescription class allows you to specify more parameters, so make sure to check the documentation.
对于消息描述,我们指定有效负载大小、此消息中包含的信号描述列表以及唯一标识符。在解码传入CAN帧时,唯一标识符将用于选择正确的消息描述。与信号描述一样,QCanMessageDescription类允许指定更多参数,因此请务必查看文档
Frame Processor
帧处理器
Once we have created the description for a unique identifier and for the message, we can create an instance of QCanFrameProcessor.
一旦我们为唯一标识符和消息创建了描述,我们就可以创建QCanFrameProcessor的实例。
QCanFrameProcessor processor;
processor.setUniqueIdDescription(uid);
processor.setMessageDescriptions({msg});
The frame processor is initialized with the previously-generated unique id description and a list of message descriptions. In our case, the list contains only one element.
帧处理器用先前生成的唯一id描述和消息描述列表进行初始化。在我们的例子中,列表只包含一个元素。
Processing CAN Frames
处理CAN帧
This section describes how the above message description can be used to parse incoming and encode new frames.
本节描述了如何使用上述消息描述来解析传入帧和对新帧进行编码。
For simplicity, let's create a CAN frame manually.
为了简单起见,让我们手动创建一个CAN帧。
QCanBusFrame frame(0x123, QByteArray::fromHex("ABCD123456780000"));
In practice, such frames will be received from a QCanBusDevice. Note that the frame has a unique identifier which matches the unique identifier of the message description which was created earlier.
在实践中,这样的帧将从QCanBusDevice接收。请注意,该帧具有与先前创建的消息描述的唯一标识符相匹配的唯一标识符。
To parse this frame, simply call a parseFrame() method:
要解析此帧,只需调用parseFrame()方法:
QCanFrameProcessor::ParseResult result = processor.parseFrame(frame);
qDebug() << Qt::hex << Qt::showbase << Qt::uppercasedigits
<< "Unique ID:" << result.uniqueId << Qt::endl
<< "Values:" << result.signalValues;
This method returns a ParseResult struct which contains a unique identifier and a map holding signal names and signal values. The output of the qDebug()
call is shown below.
此方法返回一个ParseResult结构,该结构包含一个唯一标识符和一个包含信号名称和信号值的映射。qDebug()调用的输出如下所示。
Unique ID: 0x123
Values: QMap(("signal 0", QVariant(qulonglong, 0xCDAB))("signal 1", QVariant(qlonglong, 0x78563412)))
To generate a frame, we need to call the prepareFrame() method, and pass a unique identifier and a map of signal names and signal values as parameters. The signal names must match those of the signal descriptions. For this example, we will re-use the values returned by the parseFrame() method.
要生成一个帧,我们需要调用prepareFrame()方法,并传递一个唯一的标识符和一个信号名称和信号值的映射作为参数。信号名称必须与信号描述的名称相匹配。对于这个例子,我们将重用parseFrame()方法返回的值。
QCanBusFrame generated = processor.prepareFrame(result.uniqueId,
result.signalValues);
qDebug() << Qt::hex << Qt::showbase << Qt::uppercasedigits
<< generated.frameId() << generated.payload().toHex().toUpper();
The generated frame should be similar to the initial one. And that's what we see in the qDebug()
output.
生成的帧应该与初始帧相似。这就是我们在qDebug()输出中看到的。
0x123 "ABCD123456780000"
The example in the previous section shows how to manually specify CAN message descriptions. This approach is rather verbose and error-prone.
上一节中的示例显示了如何手动指定CAN消息描述。这种方法相当冗长且容易出错。
Can we do better?
我们能做得更好吗?
Luckily, there are already some well-known standards for describing CAN bus parameters. One such standard is DBC. It is a text-based format that is widely used in various industries.
幸运的是,已经有一些众所周知的标准来描述CAN总线参数。DBC就是这样一个标准。它是一种基于文本的格式,广泛应用于各个行业。
In Qt 6.5, we have introduced a QCanDbcFileParser class. This class parses an input DBC file, and automatically generates the message descriptions. The DBC format also contains well-defined requirements for unique identifier, so the class also has a static method to generate the unique identifier description.
在Qt 6.5中,我们引入了一个QCanDbcFileParser类。这个类解析输入的DBC文件,并自动生成消息描述。DBC格式还包含对唯一标识符的定义良好的要求,因此类也有一个静态方法来生成唯一标识符描述。
This example can illustrate the typical pattern for using this class.
这个例子可以说明使用这个类的典型模式。
QCanDbcFileParser dbcParser;
if (dbcParser.parse("path/to/file.dbc")) {
QCanFrameProcessor processor;
processor.setUniqueIdDescription(QCanDbcFileParser::uniqueIdDescription());
processor.setMessageDescriptions(dbcParser.messageDescriptions());
// Do the actual processing
} else {
// Failed to extract data from DBC file
qDebug() << "Got error:" << dbcParser.error();
qDebug() << "Error details:" << dbcParser.errorString();
}
If the DBC file parsing is successful, we can use the generated message and unique identifier descriptions to create a frame processor and start processing. If the parsing fails, the API provides some convenient methods to handle the errors.
如果DBC文件解析成功,我们可以使用生成的消息和唯一标识符描述来创建帧处理器并开始处理。如果解析失败,API会提供一些方便的方法来处理错误。
Thanks for reading that far. The new APIs are released as Technical Preview, so your feedback is extremely valuable to us. If you encounter any bugs or have any suggestions for improving the API, please submit your reports in our bugtracker under the SerialBus: CAN Bus
component.
感谢您阅读本文。新的API以技术预览版的形式发布,因此您的反馈对我们非常有价值。如果您遇到任何错误或有任何改进API的建议,请在我们的错误跟踪器中的SerialBus:CAN总线组件下提交您的报告。