{"id":4772,"date":"2026-09-30T08:32:01","date_gmt":"2026-09-30T08:32:01","guid":{"rendered":"https:\/\/blog.embeddedexpert.io\/?p=4772"},"modified":"2026-09-30T08:32:38","modified_gmt":"2026-09-30T08:32:38","slug":"uart-based-sensor-emulation-part-2-developing-the-communication-protocol","status":"publish","type":"post","link":"https:\/\/blog.embeddedexpert.io\/?p=4772","title":{"rendered":"UART Based Sensor Emulation Part 3: Developing the Communication Protocol"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"559\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-1024x559.png\" alt=\"\" class=\"wp-image-4773\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-1024x559.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-300x164.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-768x419.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-1150x627.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-750x409.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-400x218.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc-250x136.png 250w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/Gemini_Generated_Image_vetcfvvetcfvvetc.png 1408w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>This guide walks through building a lightweight, framed UART protocol for emulating a sensor, covering the frame layout, parser design, and error handling needed to reliably transfer configuration data between a master MCU and an emulated sensor. By the end, you&#8217;ll have a clean, modular\u00a0<code>.c<\/code>\/<code>.h<\/code>\u00a0implementation you can drop into both STM32 projects and extend toward full sensor emulation.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>In this guide, we shall cover the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Protocol explanation.<\/li>\n\n\n\n<li>Protocol development.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Protocol Explanation:<\/h2>\n\n\n\n<p>Let&#8217;s step back and look at the protocol as a <em>design<\/em>, not just code. This will pay off when you start adding real sensor behavior.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"457\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-1024x457.png\" alt=\"\" class=\"wp-image-4774\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-1024x457.png 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-300x134.png 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-768x343.png 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-1536x686.png 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-2048x915.png 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-1150x514.png 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-750x335.png 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-400x179.png 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/09\/uart_protocol_frame-250x112.png 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.1. Why a Framed Protocol At All?<\/h3>\n\n\n\n<p>You could keep sending raw strings like the echo demo, but you&#8217;d hit four problems immediately:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Problem<\/th><th>Raw Bytes<\/th><th>Framed Protocol<\/th><\/tr><\/thead><tbody><tr><td><strong>Resync after a lost byte<\/strong><\/td><td>Impossible \u2014 you don&#8217;t know where a message starts<\/td><td>Start byte lets the parser re-align<\/td><\/tr><tr><td><strong>Boundary detection<\/strong><\/td><td>Only works because of <code>\\r\\n<\/code> conventions<\/td><td>Explicit length field<\/td><\/tr><tr><td><strong>Integrity<\/strong><\/td><td>None<\/td><td>Checksum<\/td><\/tr><tr><td><strong>Command routing<\/strong><\/td><td>Must parse text<\/td><td>First byte = command<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>A framed protocol is basically a <strong>contract<\/strong> between both sides that says: <em>&#8220;If you see this exact byte layout, and the checksum matches, here&#8217;s what it means.&#8221;<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.2. The Anatomy of the Frame<\/h3>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\"> \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 START\u2502 CMD  \u2502 LEN  \u2502  PAYLOAD  \u2502  CRC  \u2502\n \u2502 0xAA \u2502 1 B  \u2502 1 B  \u2502  N bytes  \u2502 1 B   \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n    1      1      1       0..32        1    = 4..36 bytes total<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\">1.2.1 Start Byte \u2014 <code>0xAA<\/code><\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Role<\/strong>: tells the parser &#8220;a new frame starts here.&#8221;<\/li>\n\n\n\n<li><strong>Why <code>0xAA<\/code>?<\/strong> Alternating bit pattern (10101010) is easy to spot on a scope and unlikely to appear naturally in ASCII config data.<\/li>\n\n\n\n<li><strong>Trade-off<\/strong>: a byte-stuffing scheme isn&#8217;t used (yet). If <code>0xAA<\/code> appears inside the payload, a naive parser could get confused. For <strong>bounded, length-prefixed frames<\/strong> this is fine \u2014 we trust the length field and CRC to reject false positives.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">1.2.2 Command Byte<\/h4>\n\n\n\n<p>Encodes the <em>verb<\/em> of the message. Currently:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Value<\/th><th>Name<\/th><th>Direction<\/th><th>Meaning<\/th><\/tr><\/thead><tbody><tr><td><code>0x01<\/code><\/td><td><code>CFG_CMD_WRITE<\/code><\/td><td>Master \u2192 Sensor<\/td><td>Write config registers<\/td><\/tr><tr><td><code>0x02<\/code><\/td><td><code>CFG_CMD_READ<\/code><\/td><td>Master \u2192 Sensor<\/td><td>Read back config<\/td><\/tr><tr><td><code>0x06<\/code><\/td><td><code>CFG_ACK<\/code><\/td><td>Sensor \u2192 Master<\/td><td>Success response<\/td><\/tr><tr><td><code>0x15<\/code><\/td><td><code>CFG_NACK<\/code><\/td><td>Sensor \u2192 Master<\/td><td>Failure response<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>Important subtlety<\/strong>: ACK\/NACK are single bytes, <em>not<\/em> framed. That&#8217;s a deliberate simplification \u2014 they carry no data, only status. When you extend to <code>CMD_READ<\/code>, the response <em>will<\/em> be a full frame (because it carries data).<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.2.3 Length Field<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li>One byte = max <strong>255 bytes of payload<\/strong>, but we cap at <code>CFG_MAX_PAYLOAD = 32<\/code>.<\/li>\n\n\n\n<li><strong>Why cap at 32?<\/strong> Keeps the DMA RX buffer small, bounded, and cache-friendly on the STM32F4.<\/li>\n\n\n\n<li><strong>Why is length redundant with DMA&#8217;s <code>Size<\/code>?<\/strong> Because <code>Size<\/code> reflects <em>how many bytes the UART saw<\/em>, not <em>how many belong to this frame<\/em>. If the master sends garbage first, the length field is the only reliable boundary. Belt and suspenders.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">1.2.4 Payload<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Variable-size<\/strong>, up to <code>CFG_MAX_PAYLOAD<\/code>.<\/li>\n\n\n\n<li>Currently the payload for <code>CMD_WRITE<\/code> is <code>[sample_rate, threshold]<\/code> \u2014 2 bytes.<\/li>\n\n\n\n<li><strong>Design principle<\/strong>: the <em>meaning<\/em> of payload bytes is entirely determined by the command byte. This means one command can carry multiple different data layouts without changing the frame structure.<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">1.2.5 Checksum \u2014 XOR<\/h4>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">crc = buf[0] ^ buf[1] ^ ... ^ buf[3+N-1]<\/pre><\/div>\n\n\n\n<p>The CRC byte is placed <strong>after<\/strong> the payload and covers <strong>everything from START through the last payload byte<\/strong>.<\/p>\n\n\n\n<p><strong>Properties of XOR:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Detects<\/strong>: any single-byte error, any odd number of bit errors in the frame.<\/li>\n\n\n\n<li><strong>Misses<\/strong>: an even number of bit errors that cancel out. E.g., flipping bit 3 of byte 2 and bit 3 of byte 5 produces the same XOR.<\/li>\n\n\n\n<li><strong>Cost<\/strong>: 1 byte, ~N cycles, trivial to implement.<\/li>\n<\/ul>\n\n\n\n<p><strong>When to upgrade to CRC-16:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Once you start streaming <strong>sensor data at high rates<\/strong>.<\/li>\n\n\n\n<li>When a corrupted-but-valid frame would cause a <strong>safety or calibration issue<\/strong>.<\/li>\n\n\n\n<li>When frames exceed ~16 bytes (XOR&#8217;s miss probability rises with size).<\/li>\n<\/ul>\n\n\n\n<p>CRC-16\/CCITT costs ~2\u00d7 the bytes and ~50\u00d7 the cycles but is standard practice. For configuration frames, XOR is fine.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.3. The Parsing State Machine<\/h3>\n\n\n\n<p>Even though the current parser is &#8220;stateless&#8221; (it examines a complete buffer), it helps to think of it as a state machine. This is what you&#8217;d implement later if you move to <strong>byte-by-byte<\/strong> parsing:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n       \u2502  WAIT_START \u2502\u25c4\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518             \u2502\n              \u2502 byte == 0xAA       \u2502\n              \u25bc                    \u2502\n       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510             \u2502\n       \u2502  WAIT_CMD   \u2502             \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518             \u2502\n              \u2502                    \u2502\n              \u25bc                    \u2502\n       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510             \u2502\n       \u2502  WAIT_LEN   \u2502             \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518             \u2502\n              \u2502                    \u2502\n              \u25bc                    \u2502\n       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510             \u2502\n       \u2502 WAIT_PAYLOAD\u2502             \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518             \u2502\n              \u2502 N bytes received   \u2502\n              \u25bc                    \u2502\n       \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510             \u2502\n       \u2502 WAIT_CRC    \u2502             \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518             \u2502\n              \u2502 CRC ok \u2192 process   \u2502\n              \u2502 CRC bad \u2192 NACK \u2500\u2500\u2500\u2500\u2518\n              \u2502\n              \u25bc\n           process(cmd, payload)<\/pre><\/div>\n\n\n\n<p><strong>Why this matters<\/strong>: the current DMA+IDLE approach gives you <em>whole chunks<\/em> of bytes at a time. If the master sends a partial frame (e.g., due to a buffer glitch), the current parser rejects it \u2014 but a state machine would <em>wait<\/em> for the rest. For a config protocol where frames are short and infrequent, the simple buffer-based parser is the right choice.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.4. Error Handling: What Happens When Things Go Wrong?<\/h3>\n\n\n\n<p>There are six ways a frame can be rejected, each mapping to a <code>cfg_status_t<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Error<\/th><th>Cause<\/th><th>Response<\/th><\/tr><\/thead><tbody><tr><td><code>CFG_ERR_TOO_SHORT<\/code><\/td><td>Fewer than 4 bytes<\/td><td>NACK<\/td><\/tr><tr><td><code>CFG_ERR_BAD_START<\/code><\/td><td>First byte \u2260 <code>0xAA<\/code><\/td><td>NACK<\/td><\/tr><tr><td><code>CFG_ERR_TOO_LONG<\/code><\/td><td>Length &gt; <code>CFG_MAX_PAYLOAD<\/code><\/td><td>NACK<\/td><\/tr><tr><td><code>CFG_ERR_BAD_CRC<\/code><\/td><td>Checksum mismatch<\/td><td>NACK<\/td><\/tr><tr><td><code>CFG_ERR_UNKNOWN_CMD<\/code><\/td><td>Command not recognized<\/td><td>NACK<\/td><\/tr><tr><td><code>CFG_ERR_BAD_PAYLOAD<\/code><\/td><td>Payload size wrong for command<\/td><td>NACK<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p><strong>Design decision<\/strong>: should we distinguish these on the wire? Currently <strong>no<\/strong> \u2014 all errors return <code>0x15<\/code>. That&#8217;s a <strong>simplification<\/strong> that trades debuggability for a smaller command set.<\/p>\n\n\n\n<p><strong>When to add error codes<\/strong>: once you have multiple failure modes that need different recovery actions on the master (e.g., &#8220;CRC error \u2192 retry&#8221; vs. &#8220;unknown command \u2192 don&#8217;t retry&#8221;). Then you&#8217;d extend NACK into a full frame: <code>[0xAA, 0x15, 0x01, error_code, CRC]<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.5. Timing &amp; Flow Control<\/h3>\n\n\n\n<p>Right now there is <strong>none<\/strong>, and that&#8217;s actually OK for configuration.<\/p>\n\n\n\n<p><strong>Why?<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Config frames are <strong>small<\/strong> (\u2264 36 bytes) and <strong>infrequent<\/strong> (once per boot, or on demand).<\/li>\n\n\n\n<li>The emulator replies with <strong>a single byte<\/strong> (ACK\/NACK) \u2014 near-instant.<\/li>\n\n\n\n<li>The master sends its next frame only after <code>HAL_Delay(500)<\/code>.<\/li>\n<\/ul>\n\n\n\n<p><strong>What would force you to add flow control:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Multi-frame configs (e.g., a 100-byte calibration table split across frames).<\/li>\n\n\n\n<li>Simultaneous reads and writes.<\/li>\n\n\n\n<li>If the sensor ever produces data <em>on its own<\/em> (streaming mode), you&#8217;d need to interleave config commands with data frames \u2014 which is where a proper <strong>master\/slave polling model<\/strong> like Modbus comes in.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.6. Protocol Extension Paths<\/h3>\n\n\n\n<p>Here are the natural next steps, in order of increasing complexity:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.1 Read-Back (Easy)<\/h4>\n\n\n\n<p>Extend <code>CMD_READ<\/code> so the emulator builds a response frame:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">Master \u2192 Sensor:  0xAA 0x02 0x00 CRC\nSensor \u2192 Master:  0xAA 0x82 0x02 [rate] [thr] CRC<\/pre><\/div>\n\n\n\n<p>Note <code>0x82 = 0x02 | 0x80<\/code> \u2014 the high bit marks &#8220;response&#8221; so the same command code is reused.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.2 Register Addressing (Medium)<\/h4>\n\n\n\n<p>Payload becomes <code>[addr, value]<\/code> for writes and <code>[addr]<\/code> for reads:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">CMD_WRITE payload = [0x00, 20]    \/\/ write 20 to register 0 (sample_rate)\nCMD_WRITE payload = [0x01, 75]    \/\/ write 75 to register 1 (threshold)<\/pre><\/div>\n\n\n\n<p>This decouples the protocol from the specific config layout \u2014 you can add registers without changing the frame.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.3 Multi-Write (Medium)<\/h4>\n\n\n\n<p>Payload = <code>[addr0, val0, addr1, val1, ...]<\/code> \u2014 one frame configures everything.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.4 CRC-16 (Medium)<\/h4>\n\n\n\n<p>Replace XOR with CRC-16\/CCITT and expand CRC field to 2 bytes.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.5 Sequence Numbers (Harder)<\/h4>\n\n\n\n<p>Add a <code>seq<\/code> byte so the master can match responses and detect dropped frames:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">[START, CMD, SEQ, LEN, PAYLOAD..., CRC]<\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\">1.6.6 Modbus RTU (Full)<\/h4>\n\n\n\n<p>At that point you&#8217;re essentially reimplementing Modbus RTU. If your target sensor uses Modbus, <strong>just use Modbus<\/strong> \u2014 don&#8217;t reinvent it. The frame format you have now is a good mental stepping stone toward it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">1.7. Design Rules Worth Internalizing<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>START + LEN + CRC is the minimum viable frame.<\/strong> Without all three, you&#8217;re gambling.<\/li>\n\n\n\n<li><strong>The command byte owns the payload semantics.<\/strong> Never let payload interpretation depend on anything else.<\/li>\n\n\n\n<li><strong>ACK\/NACK as single bytes is fine for config.<\/strong> Don&#8217;t over-engineer until you have a reason.<\/li>\n\n\n\n<li><strong>Reserve the high bit of the command byte<\/strong> for &#8220;this is a response&#8221; \u2014 you&#8217;ll thank yourself later.<\/li>\n\n\n\n<li><strong>Cap payload length in the header, not in the parser.<\/strong> <code>CFG_MAX_PAYLOAD<\/code> should be a compile-time constant shared by both sides.<\/li>\n\n\n\n<li><strong>Bytes are big-endian unless you decide otherwise.<\/strong> For config data it rarely matters, but pick a convention now. This protocol is endian-agnostic because all fields are 1 byte \u2014 keep it that way as long as possible.<\/li>\n\n\n\n<li><strong>XOR checksum until you have a reason for CRC-16.<\/strong> Reason = streaming data, or fields where a corrupted-but-valid frame causes harm.<\/li>\n\n\n\n<li><strong>Test the parser in isolation.<\/strong> Write a host-side C test that feeds it random byte sequences and confirms it never crashes. STM32 debugging is slow; PC debugging is fast.<\/li>\n<\/ol>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Protocol Development:<\/h2>\n\n\n\n<p>Open both, the emulated sensor and master projects.<\/p>\n\n\n\n<p>We start by creating new header and source file with name of config_protocol.h and config_protocol.h respectively.<\/p>\n\n\n\n<p>To create the header file, right click on inc folder, and select new, header file as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"596\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-1024x596.jpg\" alt=\"\" class=\"wp-image-4663\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-1024x596.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-300x175.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-768x447.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-1536x894.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-2048x1191.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-1150x669.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-750x436.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-400x233.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-18-12-250x145.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Give it a name config_protocol.h and click on Finish.<\/p>\n\n\n\n<p>To create new source file, right click on src folder, new and Source file as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"596\" src=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-1024x596.jpg\" alt=\"\" class=\"wp-image-4664\" srcset=\"https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-1024x596.jpg 1024w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-300x175.jpg 300w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-768x447.jpg 768w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-1536x894.jpg 1536w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-2048x1191.jpg 2048w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-1150x669.jpg 1150w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-750x436.jpg 750w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-400x233.jpg 400w, https:\/\/blog.embeddedexpert.io\/wp-content\/uploads\/2026\/08\/2026-08-14_15-19-49-250x145.jpg 250w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Give it a name config_protocol.c and click on Finish.<\/p>\n\n\n\n<p>Next, in the header:<\/p>\n\n\n\n<p>Include both stdint and stdbool header files:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#include &lt;stdint.h&gt;\n#include &lt;stdbool.h&gt;<\/pre><\/div>\n\n\n\n<p>Declare the frame constant:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* ---- Frame constants ---- *\/\n#define CFG_START_BYTE      0xAA\n#define CFG_CMD_WRITE       0x01\n#define CFG_CMD_READ        0x02\n#define CFG_ACK             0x06\n#define CFG_NACK            0x15\n#define CFG_MAX_PAYLOAD     32<\/pre><\/div>\n\n\n\n<p>Total frame size:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* Total frame = START(1) + CMD(1) + LEN(1) + PAYLOAD(N) + CRC(1) *\/\n#define CFG_FRAME_OVERHEAD  4\n#define CFG_MAX_FRAME       (CFG_MAX_PAYLOAD + CFG_FRAME_OVERHEAD)<\/pre><\/div>\n\n\n\n<p>Next, Results code:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* ---- Result codes ---- *\/\ntypedef enum {\n    CFG_OK = 0,\n    CFG_ERR_TOO_SHORT,\n    CFG_ERR_BAD_START,\n    CFG_ERR_TOO_LONG,\n    CFG_ERR_BAD_CRC,\n    CFG_ERR_UNKNOWN_CMD,\n    CFG_ERR_BAD_PAYLOAD\n} cfg_status_t;<\/pre><\/div>\n\n\n\n<p>Parsed Frame view:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">\/* ---- Parsed frame view ---- *\/\ntypedef struct {\n    uint8_t  cmd;\n    uint8_t  length;\n    uint8_t  payload[CFG_MAX_PAYLOAD];\n} cfg_frame_t;<\/pre><\/div>\n\n\n\n<p>Next, checksum function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint8_t cfg_checksum(const uint8_t *data, uint16_t len);<\/pre><\/div>\n\n\n\n<p>Next, frame builder:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint16_t cfg_build_frame(uint8_t *out,\n                         uint8_t cmd,\n                         const uint8_t *payload,\n                         uint8_t payload_len);<\/pre><\/div>\n\n\n\n<p>Finally, frame parser:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">cfg_status_t cfg_parse_frame(const uint8_t *buf,\n                             uint16_t size,\n                             cfg_frame_t *frame);<\/pre><\/div>\n\n\n\n<p>Hence, the header file as follows:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#ifndef INC_CONFIG_PROTOCOL_H_\n#define INC_CONFIG_PROTOCOL_H_\n\n#include &lt;stdint.h&gt;\n#include &lt;stdbool.h&gt;\n\n\/* ---- Frame constants ---- *\/\n#define CFG_START_BYTE      0xAA\n#define CFG_CMD_WRITE       0x01\n#define CFG_CMD_READ        0x02\n#define CFG_ACK             0x06\n#define CFG_NACK            0x15\n#define CFG_MAX_PAYLOAD     32\n\n\/* Total frame = START(1) + CMD(1) + LEN(1) + PAYLOAD(N) + CRC(1) *\/\n#define CFG_FRAME_OVERHEAD  4\n#define CFG_MAX_FRAME       (CFG_MAX_PAYLOAD + CFG_FRAME_OVERHEAD)\n\n\/* ---- Result codes ---- *\/\ntypedef enum {\n    CFG_OK = 0,\n    CFG_ERR_TOO_SHORT,\n    CFG_ERR_BAD_START,\n    CFG_ERR_TOO_LONG,\n    CFG_ERR_BAD_CRC,\n    CFG_ERR_UNKNOWN_CMD,\n    CFG_ERR_BAD_PAYLOAD\n} cfg_status_t;\n\n\/* ---- Parsed frame view ---- *\/\ntypedef struct {\n    uint8_t  cmd;\n    uint8_t  length;\n    uint8_t  payload[CFG_MAX_PAYLOAD];\n} cfg_frame_t;\n\n\/* ---- Checksum ---- *\/\nuint8_t cfg_checksum(const uint8_t *data, uint16_t len);\n\n\/* ---- Frame builder: returns total bytes written (0 on error) ---- *\/\nuint16_t cfg_build_frame(uint8_t *out,\n                         uint8_t cmd,\n                         const uint8_t *payload,\n                         uint8_t payload_len);\n\n\/* ---- Frame parser: fills `frame` on success ---- *\/\ncfg_status_t cfg_parse_frame(const uint8_t *buf,\n                             uint16_t size,\n                             cfg_frame_t *frame);<\/pre><\/div>\n\n\n\n<p>Next, in  config_protocol.c source file:<\/p>\n\n\n\n<p>Include the following header file:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">#include &quot;config_protocol.h&quot;\n#include &lt;string.h&gt;<\/pre><\/div>\n\n\n\n<p>Next, the check sum function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint8_t cfg_checksum(const uint8_t *data, uint16_t len)\n{\n    uint8_t crc = 0;\n    for (uint16_t i = 0; i &lt; len; i++) {\n        crc ^= data[i];\n    }\n    return crc;<\/pre><\/div>\n\n\n\n<p>Next, build frame function:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">uint16_t cfg_build_frame(uint8_t *out,\n                         uint8_t cmd,\n                         const uint8_t *payload,\n                         uint8_t payload_len)\n{\n    if (out == NULL) return 0;\n    if (payload_len &gt; CFG_MAX_PAYLOAD) return 0;\n    if ((payload_len &gt; 0) &amp;&amp; (payload == NULL)) return 0;\n\n    uint16_t idx = 0;\n    out[idx++] = CFG_START_BYTE;\n    out[idx++] = cmd;\n    out[idx++] = payload_len;\n\n    for (uint8_t i = 0; i &lt; payload_len; i++) {\n        out[idx++] = payload[i];\n    }\n\n    out[idx] = cfg_checksum(out, idx);\n    idx++;\n\n    return idx;\n}<\/pre><\/div>\n\n\n\n<p>Finally, parse frame:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting=\"{&quot;showPanel&quot;:true,&quot;languageLabel&quot;:&quot;language&quot;,&quot;fullScreenButton&quot;:true,&quot;copyButton&quot;:true,&quot;mode&quot;:&quot;clike&quot;,&quot;mime&quot;:&quot;text\/x-csrc&quot;,&quot;theme&quot;:&quot;dracula&quot;,&quot;lineNumbers&quot;:false,&quot;styleActiveLine&quot;:false,&quot;lineWrapping&quot;:false,&quot;readOnly&quot;:true,&quot;fileName&quot;:&quot;C&quot;,&quot;language&quot;:&quot;C&quot;,&quot;maxHeight&quot;:&quot;400px&quot;,&quot;modeName&quot;:&quot;c&quot;}\">cfg_status_t cfg_parse_frame(const uint8_t *buf,\n                             uint16_t size,\n                             cfg_frame_t *frame)\n{\n    if ((buf == NULL) || (frame == NULL)) return CFG_ERR_TOO_SHORT;\n    if (size &lt; CFG_FRAME_OVERHEAD)       return CFG_ERR_TOO_SHORT;\n    if (buf[0] != CFG_START_BYTE)        return CFG_ERR_BAD_START;\n\n    uint8_t cmd     = buf[1];\n    uint8_t payload = buf[2];\n\n    if (payload &gt; CFG_MAX_PAYLOAD)       return CFG_ERR_TOO_LONG;\n    if (size &lt; (uint16_t)(payload + CFG_FRAME_OVERHEAD))\n                                         return CFG_ERR_TOO_SHORT;\n\n    \/* Checksum covers START..last payload byte *\/\n    uint8_t expected = cfg_checksum(buf, (uint16_t)(payload + 3));\n    if (expected != buf[3 + payload])    return CFG_ERR_BAD_CRC;\n\n    frame-&gt;cmd    = cmd;\n    frame-&gt;length = payload;\n    if (payload &gt; 0) {\n        memcpy(frame-&gt;payload, &amp;buf[3], payload);\n    }\n    return CFG_OK;\n}<\/pre><\/div>\n\n\n\n<p>Thats all for the protocol.<\/p>\n\n\n\n<p>Next part will focus on implementing the sensor configuration on emulated MCU.<\/p>\n\n\n\n<p>Stay tuned.<\/p>\n\n\n\n<p>Happy coding \ud83d\ude09<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This guide walks through building a lightweight, framed UART protocol for emulating a sensor, covering the frame layout, parser design, and error handling needed to reliably transfer configuration data between a master MCU and an emulated sensor. By the end, you&#8217;ll have a clean, modular\u00a0.c\/.h\u00a0implementation you can drop into both STM32 projects and extend toward [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,11,12],"tags":[],"class_list":["post-4772","post","type-post","status-publish","format-standard","hentry","category-embedded-systems","category-peripheral-drivers","category-stm32"],"_links":{"self":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4772"}],"collection":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4772"}],"version-history":[{"count":2,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4772\/revisions"}],"predecessor-version":[{"id":4776,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=\/wp\/v2\/posts\/4772\/revisions\/4776"}],"wp:attachment":[{"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4772"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4772"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.embeddedexpert.io\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4772"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}