{"id":880,"date":"2026-04-14T16:29:02","date_gmt":"2026-04-14T14:29:02","guid":{"rendered":"https:\/\/thurow.de\/?p=880"},"modified":"2026-04-14T18:31:14","modified_gmt":"2026-04-14T16:31:14","slug":"projektbeispiel-vermessungsgeraet-teil-5","status":"publish","type":"post","link":"https:\/\/thurow.de\/?p=880","title":{"rendered":"Projektbeispiel Vermessungsger\u00e4t Teil 5"},"content":{"rendered":"\n<p><a href=\"https:\/\/thurow.de\/?p=619\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 1<\/a> widmete sich der Aufsetzung der Infrastruktur f\u00fcr dieses Entwicklungsprojekt.<br><a href=\"https:\/\/thurow.de\/?p=664\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 2<\/a> widmete sich den Grundlagen der Drehgebereinbindung.<br><a href=\"https:\/\/thurow.de\/?p=668\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 3<\/a> beleuchtete die Hardwareanbindung des BiSS \/ ACURO Busses an SoCs.<br><a href=\"https:\/\/thurow.de\/?p=721\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 4<\/a> endete mit dem darauf laufenden Bus-Protokoll konkret f\u00fcr den ausgesuchten Drehgeber.<\/p>\n\n\n\n<p>Dieser Teil widmet sich nun der unteren Softwareschichten, der Anbindung von BiSS via SPI und die Extraktion der Messwerte aus den Paketen samt CRC.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img fetchpriority=\"high\" decoding=\"async\" width=\"815\" height=\"324\" src=\"https:\/\/thurow.de\/wp-content\/uploads\/2024\/11\/Zeiten-ACURO-BiSS-Modus.drawio-2.svg\" alt=\"\" class=\"wp-image-730\"\/><figcaption class=\"wp-element-caption\">Definition der Zeitgruppen T1 bis T5<\/figcaption><\/figure>\n\n\n\n<p>An dieser Stelle eine Kurzzusammenfassung der Zeiten aus <a href=\"https:\/\/thurow.de\/?p=721\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 4<\/a>:<\/p>\n\n\n\n<p>T1 enth\u00e4lt zwei fallende Flanken, w\u00e4hrend SL high ist, also 0b11.<\/p>\n\n\n\n<p>T2 enth\u00e4lt mindestens eine fallende Flanke. Dazu kommt die Zeit \\(busy\\). Diese betr\u00e4gt maximal 5,2\u00b5s. Damit ergibt sich f\u00fcr T2 eine Anzahl zwischen 1 und \\(SPI_{Frequenz} \\cdot busy + 1\\) Bits. Das entspricht bei 100kHz einem, bei 400kHz drei, bei 1MHz 6 und bei 8MHz 42 Bits.<\/p>\n\n\n\n<p>T3 besteht aus einem Bit mit Wert 1.<\/p>\n\n\n\n<p>T4 enth\u00e4lt bei dem gew\u00e4hlten Drehgeber 42 Bits, welche Daten-, F\u00fcll- Warn- und CRC-Bits enthalten.<\/p>\n\n\n\n<p>T5 ist direkt abh\u00e4ngig von \\(timeout_{SENS}\\). Diese liegt als default zwischen 9,9 und 14,9\u00b5s. Bei 1MHz sind das zwischen 10 und 15 Bits.<\/p>\n\n\n\n<p>Im genannten Anwendungsfall gen\u00fcgte ein Takt von 1MHz. Und dies f\u00fchrte zu einer willkommenen Konsequenz: die Gruppen T1 bis T4 passen in jedem Fall in 8 Byte und damit in einen 64-Bit-Integer. Dieser Umstand macht die Extraktion auf den wegen Batterieversorgung leistungschwachen SoCs sehr effizient, per Bitshiftung.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n  uint8_t buffer&#x5B;8];\n  ret = spi_read_packet(buffer, 64);\n  if (ret != RET_OK)\n    return ret;\n\n  \/\/ Wir m\u00fcssen die Byte Order umkehren, da der SoC Little Endian ist, Acuro Big Endian. Das ganze machen wir LINKSB\u00dcNDIG in einen UInt64\n  uint64_t bit_vector = 0;\n  uint8_t* packet = (uint8_t*)&amp;bit_vector;\n  for (int pos = 0; pos &lt; 8; pos++)\n    packet&#x5B;7-pos] = buffer&#x5B;pos];\n\n  \/\/ so, nun m\u00fcssen wir die &quot;Startsequenz&quot; des Acuro-Pakets wegschneiden. Zun\u00e4chst zwei f\u00fchrende 1 nach Spezifikation\n  bool isValid = false;\n  size_t position = 0;\n  for (int pos = 0; pos &lt; 4; pos++)\n  {\n    if (!(bit_vector &amp; highest_bit))\n    {\n      isValid = true;\n      break;\n    }\n\n    bit_vector &lt;&lt;= 1;\n    position++;\n  }\n  \n  if (!isValid)\n    return ERR_ACURO_PROT_1;\n\n  \/\/ nun muss der Sensor mit low quitiert haben. Variabel ist aber seine busy Zeit, in der er low bleibt. Wir schneiden nun f\u00fchrende 0 weg, auch deren Anzahl ist durch max busy time begrenzt, was wir nat\u00fcrlich testen.\n  isValid = false;\n  for (int pos = 0; pos &lt; 6; pos++)\n  {\n    if (bit_vector &amp; highest_bit)\n    {\n      isValid = true;\n      break;\n    }\n\n    bit_vector &lt;&lt;= 1;\n    position++;\n  }\n  \n  if (!isValid)\n    return ERR_ACURO_PROT_2;\n\n  \/\/ Jetzt kommt noch das Startbit des Acuro-Pakets weg.\n  bit_vector &lt;&lt;= 1;\n  position++;\n\n  \/\/ Jipi, der Rest ist nun unser Acuro Paket, linksb\u00fcndig ausgerichtet. ACHTUNG: der Winkelwert ist nat\u00fcrlich ... Little Endian!\n  \/\/ Wo wir das Paket so sch\u00f6n linksb\u00fcndig haben, ist es ideal ausgerichtet f\u00fcr unseren CRC check. Daher machen wir uns mal hier eine Kopie f\u00fcr sp\u00e4ter.\n  uint64_t value_for_crc = bit_vector;\n\n  \/\/ Nun wollen wir den Inhalt &quot;per Maske&quot; auslesen. Daher machen wir das Paket jetzt rechtsb\u00fcndig.\t\n  bit_vector &gt;&gt;= RIGHT_SHIFT;\n  \n   \/\/ Als Maske nehmen wir der Einfach halber einen struct. Kann man auch zu Fu\u00df machen, muss man aber nicht.\t\n  const acuro_packet* a_packet = (const acuro_packet*)&amp;bit_vector;\n   \n  \/\/ So, nun machen wir auch unseren CRC Test\n  uint8_t crc = compute_crc(value_for_crc);\n<\/pre><\/div>\n\n\n<p>Wie zu sehen, wurde in diesem Fall zugunsten einfacheren Codes direkt auf dem 64-Bit Integer geshiftet. Leistungsschwache SoCs f\u00fcr Batterieanwendungen haben oft 32-Bit Register. W\u00e4hrend Shift auf 32 Bit-Registern eine Instruktion ben\u00f6tigt, kann je nach Core die Umsetzung eines 64-Bit Shifts auf diese 3 bis 6 Instruktionen ben\u00f6tigen.<\/p>\n\n\n\n<p>Die gleiche Frage betrifft den Einsatz mehrerer Drehgeber. Ideal im Sinne der Messung ist eine gleichzeitige Abfrage der Drehgeber mit einem identischen Taktsignal, wie explizit vom \u201eDrehgeberpapst\u201c Josef Siraky vorgedacht (siehe <a href=\"https:\/\/thurow.de\/?p=664\" target=\"_blank\" rel=\"noreferrer noopener\">Teil 2<\/a>).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" width=\"1255\" height=\"1230\" src=\"https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik.png\" alt=\"\" class=\"wp-image-654\" style=\"aspect-ratio:1.0203203084445849;width:451px;height:auto\" srcset=\"https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik.png 1255w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-300x294.png 300w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-768x753.png 768w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-1000x980.png 1000w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-230x225.png 230w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-350x343.png 350w, https:\/\/thurow.de\/wp-content\/uploads\/2024\/08\/grafik-480x470.png 480w\" sizes=\"(max-width: 1255px) 100vw, 1255px\" \/><figcaption class=\"wp-element-caption\">Simultane \u00dcbertragung von Absolutgebern, Figur 4 aus Patent <a href=\"https:\/\/worldwide.espacenet.com\/patent\/search\/family\/006240539\/publication\/EP0171579A1?q=pn%3DEP0171579\" target=\"_blank\" rel=\"noreferrer noopener\">EP0171579<\/a><\/figcaption><\/figure>\n\n\n\n<p>Praktisch werden Dual- \/ Quad- \/ Octal-SPI jedoch oft von den \u00fcblichen RTOS auf oberer Treiberebene nur f\u00fcr Speicheranbindungen angeboten, etwa bei nRF via <a href=\"https:\/\/www.zephyrproject.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">Zephyr<\/a>. Positiv: RTOS wie Zephyr oder FreeRTOS sind FOSS, und daher kann man hier die unteren Treiberschichten entsprechend selbst erg\u00e4nzen. Negativ: man ist hier so tief &#8222;im Maschinenraum&#8220; in der Ebene von DMA und Interruptbehandlung, das stabile Entwicklungsarbeit nur bei gro\u00dfen Abnahmemengen oder speziellen Sicherheitsklassen wirtschaftlich vertretbar ist. Und jede L\u00f6sung ist auf dieser tiefen Stufe direkt hardwareabh\u00e4ngig.<\/p>\n\n\n\n<p>Ein wesentlicher Punkt ist noch <a href=\"https:\/\/de.wikipedia.org\/wiki\/Zyklische_Redundanzpr%C3%BCfung\" target=\"_blank\" rel=\"noreferrer noopener\">CRC<\/a>. Ein Sprichwort lautet, wer billig kauft, kauft zweimal. CRC hier als obligatorisch anzusehen, bedeutet in der Realit\u00e4t, Bitkipper und damit Ausrei\u00dfer &#8222;nach oben&#8220; abzugeben. Und dort wird eine Erkennung und Ausfilterung immer teurer bis unm\u00f6glich. Solche Dinge auszulassen ist tats\u00e4chlich &#8222;Pfusch am Bau&#8220;.<\/p>\n\n\n\n<p>Die CRC Umsetzung ist ein gutes Beispiel f\u00fcr die Entwicklungen der Sprachen C und C++ in den letzten Jahren. Gerade Ver\u00e4nderungen ab C++ 17 machen sie f\u00fcr den embedded Einsatz immer attraktiver. In diesem Fall ist das Stichwort <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/constexpr.html\" target=\"_blank\" rel=\"noreferrer noopener\">constexpr<\/a>.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &lt;stdio.h&gt;\n\n\/**\n  Ein Generatorpolynom als Bitmaske direkt f\u00fcr die Verarbeitung im 64 Bit Register.\n  \n  \\author    Dr. Torsten Thurow\n*\/\ntypedef struct\n{\n  uint64_t mask;                                \/\/\/&lt; Die Bitmaske selbst, das MSB mit Wert 1 ist dabei linksb\u00fcndig ausgerichtet (Bit 63 bei Z\u00e4hlweise startend mit 0)\n  size_t length;                                \/\/\/&lt; Die Anzahl der Bits des Polynoms, entspricht dem Grad des Polynoms + 1\n} PolynomMask;\n\nconst static uint64_t testMask = ((uint64_t)1 &lt;&lt; 63); \/\/\/&lt; testMask dient der einfachen Pr\u00fcfung, ob ein 64 Bit Wert linksb\u00fcndig eine 1 besitzt (Bit 63 bei Z\u00e4hlweise startend mit 0)\n\n\/**\n  Erstellt aus einem Generatorpolynom eine linksseitig orientierte Bitmaske.\n  \n  \\author    Dr. Torsten Thurow\n  \n  \\param    polynom        Das Generatorpolynom als Wert.\n  \n  \\returns  Eine linksseitig orientierte Bitmaske generiert aus dem Generatorpolynom.\n*\/\nconstexpr PolynomMask CreatePolynomMask(uint64_t polynom)\n{\n  PolynomMask polynomMask = { polynom, 64 };    \/\/ zun\u00e4chst wird die Maske mit dem Polynomwert und dem L\u00e4ngenwert, der noch bestimmt wird, mit maximal 64 Bit, initialisiert\n  while (!(polynomMask.mask &amp; testMask))        \/\/ solange das h\u00f6chste Bit 63 (bei Z\u00e4hlweise startend mit 0) 0 ist\n  {\n    polynomMask.mask &lt;&lt;= 1;                     \/\/ schiebe die Bitsequenz eine Stelle weiter nach link\n    polynomMask.length--;                       \/\/ das bedeutet aber auch, der L\u00e4ngenwert muss wieder ein Bit geringer sein, denn das Polynom startet prinzipbedingt immer mit 1\n  }\n  return polynomMask;                           \/\/ die Maske ist fertig\n}\n\n#ifdef CRC_OUTPUT\n\/**\n  Gibt die einzelnen Bits einer Bitsequenz menschenlesbar aus.\n  \n  \\author    Dr. Torsten Thurow\n  \n  \\param    value         Die Bitsequenz als 64 Bit Integer.\n  \\param    offset        Nummer des ersten Bits, welches ausgegeben wird, in der Orientierung vom h\u00f6chstwertigen Bit zum niederwertigsten Bit.\n  \\param    length        Anzahl der Bits, welche ausgegeben werden.\n*\/\nvoid PrintBits(uint64_t value, size_t offset, size_t length)\n{\n  if (!length)\n    return;\n\n  value &lt;&lt;= offset;\n  while (true)\n  {\n    std::cout &lt;&lt; (value &amp; testMask ? &#039;1&#039; : &#039;0&#039;);\n    value &lt;&lt;= 1;\n    length--;\n    if (!length)\n      break;\n    std::cout &lt;&lt; &#039; &#039;;\n  }\n  std::cout &lt;&lt; std::endl &lt;&lt; std::flush;\n}\n#endif \/\/ CRC_OUTPUT\n\n\/**\n  Der eigentliche CRC-Algorithmus f\u00fcr die Berechnung des CRC Wertes wie auch dessen Kontrolle\n  \n  \\author    Dr. Torsten Thurow\n  \n  \\param    value          Die Bitsequenz als 64 Bit Integer.\n  \\param    length         Die L\u00e4nge der Bitsequenz als Anzahl Bits.\n  \\param    polynomMask    Das Generatorpolynom als Bitmaske.\n  \n  \\returns  An uint64_t.\n*\/\nstatic uint64_t Calculate(uint64_t value, size_t length, PolynomMask polynomMask)\n{\n  \/\/ Zuerst wird der Rahmen links ausgerichtet\n  value &lt;&lt;= 64 - length;\n  while (true)\n  {\n#ifdef CRC_OUTPUT\n    \/\/ Die Bitsequenz wird ausgegeben\n    PrintBits(value, 0, length + polynomMask.length - 1);\n#endif \/\/  CRC_OUTPUT\n    \n    \/\/ Nun werden alle f\u00fchrenden Nullen entfernt\n    while (!(value &amp; testMask)) \/\/ solange das h\u00f6chste Bit 63 (bei Z\u00e4hlweise startend mit 0) 0 ist\n    {\n      value &lt;&lt;= 1;                              \/\/ schiebe die Bitsequenz eine Stelle weiter nach link\n      length--;                                 \/\/ von der Ursprungssequenz wird nach dem Shift nach Links ein Bit weniger vorgehalten\n      if (!length)                              \/\/ wenn die gesamte Ursprungssequenz abgearbeitet wurde ...\n        return value;                           \/\/ ... verbleibt nur noch der CRC Wert linksb\u00fcndig\n    }\n\n#ifdef CRC_OUTPUT\n    PrintBits(polynomMask.mask, 0, polynomMask.length);\n#endif \/\/  CRC_OUTPUT\n    \n    value ^= polynomMask.mask;                  \/\/ hier erfolgt die XOR-Operation zwischen (Rest)Wert und Generatorpolynom\n  }\n}\n<\/pre><\/div>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Teil 1 widmete sich der Aufsetzung der Infrastruktur f\u00fcr dieses Entwicklungsprojekt.Teil 2 widmete sich den Grundlagen der Drehgebereinbindung.Teil 3 beleuchtete die Hardwareanbindung [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,23,9],"tags":[],"class_list":["post-880","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-embedded-programmierung","category-programmierung"],"_links":{"self":[{"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/posts\/880","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/thurow.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=880"}],"version-history":[{"count":11,"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/posts\/880\/revisions"}],"predecessor-version":[{"id":895,"href":"https:\/\/thurow.de\/index.php?rest_route=\/wp\/v2\/posts\/880\/revisions\/895"}],"wp:attachment":[{"href":"https:\/\/thurow.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=880"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thurow.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=880"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thurow.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=880"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}