The previous section discussed the Start-Stop-Type frame structure protocol, which constructs the frame structure using frame headers, lengths, and checksums, enabling reliable and accurate transmission of packets based on the frame structure.
-
Application Layer Packet Design Ideas
Returning to the project itself, the packets within the frame structure are what the application ultimately needs to parse and are related to specific business requirements.
This article will briefly introduce how to design interaction instructions in packets to meet specific business needs. This is just a thought to spark ideas.
Similar to the frame structure, when designing packets, fields are composed byte by byte according to the specific needs of the interaction logic, forming packets to complete command interactions.
In specific projects, there are generally target addresses, source addresses, command types, transmission directions, cascading sequence numbers, parameter ID, parameter values, etc.
The definitions of fields depend on project requirements, and the fields mentioned above may exist but are not limited to those.
The following introduces the ideas for packet design and parsing in specific projects. There are many methods in engineering practice, and I believe many experienced engineers have their own clever programming ideas. Feel free to leave comments on this page for discussion.
-
Project Case
The nRF51822 based BLE terminal device communicates with the host computer using UART, with the physical line using USB to UART.
Packet Definition
Type Definition
Parameter Name&Parameter Value Definition
Based on the above definitions, a structure can be designed for the application to parse commands, where the defined type type and parameter name para are defined using enumeration types:
-
Regular Parsing Process
The parsing function generally takes the input parameter *indata and points a new structure pointer to this input parameter. Subsequent parsing uses the structure pointer to process the data, enhancing code readability!
The definition method in the above screenshot has a warning, so a forced conversion needs to be done as follows:
Regular judgment handling often uses switch(){case :} combined with if(…){;}else(…){;} for judgment logic. The architecture of this pattern is as follows:
The above approach sequentially checks the type type and parameter name para, and then processes them directly. When the number of enumeration members for these two fields is small, this judgment can work; however, if the project needs to expand or business requirements change, then if(…){;}else(…){;} sequential checks will make the code size in the parsing function enormous!
There are several shortcomings:
1. The number of types or other branches required by business needs dictates the number of such judgment logics, turning coding into a physical task;
2. When viewing and maintaining the code, what one faces is still a long list of switch(){case :} and if(…){;}else(…){;} statements;
3. When adding or deleting functions, one needs to find the specific judgment location in the code and carefully comment or modify it.
There is no technical content here; it is basically just copying and pasting judgment statements and modifying the judgment objects, which is essentially just a lookup process!
-
Constructing a Lookup Table Parsing Method
Since we need to look up a table, of course, there will be a while() loop, incrementing a variable to perform the lookup process. Here, the types type and parameter names para defined in the packet structure can be used as lookup objects. How should we choose?
Assuming:
1. Using type as the lookup object, if the type equals the queried parameter after the lookup, then the parameter name remains a case with multiple branches, either continuing to look up or continuing to use switch(){case :} or if(…){;}else(…){;} to judge numerous different parameter names;
2. Using parameter name as the lookup object, if the parameter name equals the device operating status after the lookup, then the type needs to make at most three judgments: query, set, and others.
Comparing the two, the second one is definitely more efficient in programming and clarifying the logical framework.
To look up a table, one must build a table. The structure of the table should use parameter names para as the lookup objects and execute the lookup results in the form of callback functions. The table is defined as follows:
While it is called building a table, it is essentially defining an array of structures, where each element of the array is of structure type. Here, the structure mainly consists of the parameter names of the packet protocol and corresponding callback functions, defined as follows:
When executing packet parsing, the lookup table idea is:
1. First, create a pointer to the table structure *ptable pointing to the start position of the table, which is the first element of the array {ECHO, dcapp_dev_echo}
2. Next, create a pointer to the packet structure *pbuf pointing to the starting address of the input data
3. By incrementing the ptable pointer, compare the parameter name members of ptable and pbuf
4. Finally, execute the corresponding callback function of the ptable pointer
The above idea can be implemented in just a few lines of code to parse the parameter names of the input packet! Efficient and clear!
Additionally, when building the table, place invalid parameter names and their corresponding callback functions at the end. The benefit of this approach is that after checking the entire table, there is no need to distinguish whether the corresponding parameter name was found; you can directly execute the callback function corresponding to the pointer.
This way, even if the parameter name is not found, the last element in the table will still execute the callback function for error parsing, dcapp_parser_err().
With this lookup table processing method, adding or deleting command functions becomes much simpler! To add a function, just add the parameter name and corresponding callback function to the table; to delete a function, simply find the corresponding parameter name and callback function in the table!
To summarize, although the lookup table method is very clear, the corresponding callback function needs to be handled and implemented independently, and each parameter name needs to be processed separately. Compared to using switch(){case :} combined with if(…){;}else(…){;} judgment logic, it is indeed much clearer.
The above lookup table idea comes from projects I have experienced, and also references
“STM32CubeExpansion_MEMSMIC1_V1.1”
This is an official open-source project example from ST for digital microphones. When used as a USB audio device, a similar callback function method:
Debugging Screenshot
After correctly parsing the parameter names of the packets, the execution result of the corresponding functions is printed debug information, as shown in the screenshot below:
This is the initial parsing effect, which can correctly jump to the corresponding function execution through callback functions. Specific handling still needs to be designed according to the project’s business requirements, and will not be extended here.
References:
“X-CUBE-MEMSMIC1”@
http://www.stmcu.org.cn/document/detail/index/id-216480
Long press the QR code to followDigCore
Follow the public account and send a message:
Download&Packet_Parse
Get this articlePDF original text or source code download link
★★★★★Recommended Articles:
“Embedded Hardware Communication Interface – Using RingBuffer to Handle Data (Part 2) Detailed Design Process”
“Embedded Hardware Communication Protocol – UART (Part 4) Designing Start-Stop Type Application Layer Protocol”
★★★★★Extended Reading
“Quick Development of MQTT (Part 1) MQTT Through the Eyes of Electronic Engineers”
“Quick Development of MQTT (Part 2) Introduction to MQTT”
“Building an MQTT Client – The Clearest MQTT Protocol Architecture”
“Building an MQTT Server – The Fastest Way to Validate Your Developed Client”


www.digcore.cn
More technical content awaits you!
Long press the QR code to follow
