{"id":22923508,"url":"https://github.com/ams-osram/osp_aospi","last_synced_at":"2026-04-24T21:31:21.880Z","repository":{"id":247409274,"uuid":"825644900","full_name":"ams-OSRAM/OSP_aospi","owner":"ams-OSRAM","description":"OSP library that implements 2-wire SPI towards and from OSP nodes.","archived":false,"fork":false,"pushed_at":"2026-04-21T14:55:25.000Z","size":2016,"stargazers_count":1,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-21T16:41:35.999Z","etag":null,"topics":["arduino","as1163","e3731i","library","osp","rgb-led"],"latest_commit_sha":null,"homepage":"https://github.com/ams-OSRAM/OSP_aotop","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ams-OSRAM.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-07-08T08:27:39.000Z","updated_at":"2026-04-21T14:54:50.000Z","dependencies_parsed_at":"2024-09-27T14:43:25.596Z","dependency_job_id":"e73b3028-8df1-43e5-a08c-54a4f881b938","html_url":"https://github.com/ams-OSRAM/OSP_aospi","commit_stats":null,"previous_names":["ams-osram-group/osp_aospi","ams-osram/osp_aospi"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/ams-OSRAM/OSP_aospi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2FOSP_aospi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2FOSP_aospi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2FOSP_aospi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2FOSP_aospi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ams-OSRAM","download_url":"https://codeload.github.com/ams-OSRAM/OSP_aospi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ams-OSRAM%2FOSP_aospi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32241549,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","as1163","e3731i","library","osp","rgb-led"],"created_at":"2024-12-14T08:15:53.775Z","updated_at":"2026-04-24T21:31:21.871Z","avatar_url":"https://github.com/ams-OSRAM.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OSP 2wireSPI aospi\n\nLibrary \"OSP 2wireSPI aospi\", usually abbreviated to \"aospi\",\nis one of the **aolibs**; short for Arduino OSP libraries from ams-OSRAM.\nThis suite implements support for chips that use the Open System Protocol, \nlike the AS1163 (\"SAID\") or the OSIRE E3731i (\"RGBi\").\nThe landing page for the _aolibs_ is on \n[GitHub](https://github.com/ams-OSRAM/OSP_aotop).\n\n\n## Introduction\n\nLibrary _aospi_ implements 2-wire SPI towards and from OSP nodes.\n\n![aospi in context](extras/aolibs-aospi.drawio.png)\n\nIt implements the _communication layer_ between the MCU and OSP nodes;\nit allows sending and receiving telegrams (actually, raw byte arrays) using \nthe SPI blocks of the MCU.\n\nAnother library - _aoosp_ - implements how the telegrams byte arrays are formatted: \nthe encoding of the arguments into bytes, the preamble, payload size indicator,\nCRC, what response telegrams comes with which command telegram. It builds on top of _aospi_.\n\n\n## System overview\n\nThe _aospi_ library is written for the OSP32 board in the SAID evaluation kit.\nThe diagram below gives an abstract overview of the OSP32 board and how\nto connect demo boards. Demo boards are strips of OSP nodes, at the moment of\nwriting this document two types of nodes exist: the AS1163 (\"SAID\") and the \nOSIRE E3731i (\"RGBi\").\n\n![OSP32overview](extras/OSP32overview.drawio.png)\n\nThe OSP32 board contains an MCU: an ESP32 micro-controller. The MCU has two\nSPI blocks, one for sending telegrams (\"SPI OUT\") and one for receiving telegrams\n(\"SPI IN\"). The SPI OUT is wired to an on-board OSP node (\"SAID OUT\"), which \ndrives two RGB triplets and one I2C bus. The SAID OUT is wired to a connector\non the edge of the OSP32 board (\"OUT\").\n\nThe OUT connector is wired via zero or more demo boards to either a _terminator_\nor, with a _cable_, back to a second connector on the edge of the OSP32 board \n(\"IN\"). The IN connector wires to a second OSP node  (\"SAID IN\"), which\ndrives three RGB triplets.\n\nWhen the terminator is present, the chain of OSP nodes is said to be in \nbidirectional mode (\"BiDir\"). In BiDir mode responses from OSP nodes are \nsent backwards over the chain, and thus appear on sio1 of SAID OUT. \nWhen the cable is present,  the chain of OSP nodes is said to be in loop-back \nmode (\"Loop\"). In Loop mode responses from OSP nodes are sent forwards \nover the chain, and thus appear on sio2 of SAID IN.\n\nA switch known as \"DIRMUX\" either connects the sio1 of SAID OUT or \nthe sio2 of SAID IN to the SPI receiver (\"SPI IN\"). \nThe DIRMUX is controlled with a GPIO pin of the MCU.\nThere are two LEDs on the OSP32 board (labeled BIDIR and LOOP, not shown \nin the diagram) which indicate the state of the DIRMUX.\n\nNote that SAID OUT (channel 2) is wired to an I2C bus (with an on-board I2C \nEEPROM memory). This allows testing the I2C capabilities of the SAID without \nthe need to create additional hardware. Not shown in the diagram is that \nSAID IN also has a test feature: there is an option to connect channel 1/blue \nto the MCU and use that to issue a SYNC pulse. Finally note that the OSP32 \nboard has several test pins. This allows hooking up a logic analyzer, to trace \ntelegrams.\n\n\n## Examples\n\nThis library comes with the following examples.\nYou can find them in the Arduino IDE via \n[File \u003e Examples \u003e OSP 2wireSPI aospi \u003e ...](examples):\n\n- **aospi_tx** ([source](examples/aospi_tx))  \n  In this demo blinks an LED. A small set of telegrams has been hand-constructed.\n  Those telegrams are passed directly to the SPI layer (_aospi_ lib).\n  The demo uses a minimal amount of telegrams to switch on the LEDs \n  of the first SAID of the OSP32 board.\n\n- **aospi_txrx** ([source](examples/aospi_txrx))  \n  In this demo, a small set of telegrams has been hand-constructed.\n  Those telegrams are passed directly to the SPI layer (_aospi_ lib).\n  Some telegrams cause a response which is passed back from _aospi_,\n  analyzed and printed.\n  \n  The demo can be run in BiDir or Loop mode.\n\n- **aospi_time** ([source](examples/aospi_time))  \n  This demo measures, in a BiDirectional chain, the round trip time for two \n  telegrams:  READSTAT (5 byte responses) and IDENTIFY (8 byte responses).\n  Both telegrams are sent to each node in chain of length 5 (and then 10 \n  times to allow some averaging).\n  The measured times are compared with each other, to check two aspects.\n  The first is the impact of addressing nodes further in the chain \n  (adding the fast forwarding time for each intermediate node).\n  The second aspect is the impact of longer telegrams.\n\n- **aospi_bringup** ([source](examples/aospi_bringup))  \n  This sketch just sends a RESET telegram to a node. We check whether that \n  node forwards the telegram. The checking is done using a logic analyzer. \n  A next step is sending INITBIDIR. It is an example of how to bring-up\n  new hardware/software. There is a more detailed \n  [readme.md](examples/aospi_bringup/readme.md) to explain the process.\n\n- **aospi_mcua** ([source](examples/aospi_mcua))\n  This is an advanced demo: we reconfigure the OSP32 board (V11 or higher is \n  needed) to bypass the SAID OUT which uses MCU mode type B. Instead we attach \n  the SAIDbasic board directly to the level shifter. This allows us to test the \n  MCU mode type A. MCU mode type A is used in RGBI and in SAID with a default \n  OTP image. The [readme.md](examples/aospi_mcua/readme.md) explains how to\n  reconfigure OSP32.\n \n\n## API\n\nThe header [aospi.h](src/aospi.h) contains the API of this library.\nThe header contains little documentation; for that see the\n[aospi.cpp](src/aospi.cpp) source file. \n\nHere is a quick overview:\n\n- `aospi_tx(...)` sends a command telegram.\n\n- `aospi_txrx(...)` sends a command telegram and receives the response.\n\n- `aospi_dirmux_set_loop()` and `aospi_dirmux_set_bidir()` set the direction \n  mux to Loop respectively BiDir. One of these functions is typically \n  called once during OSP chain initialization time, but must be\n  called before using `aospi_txrx()` (since that uses the direction mux).\n  There are also observers of the current direction mux setting \n  (`aospi_dirmux_is_loop()` and `aospi_dirmux_is_bidir()`).\n\n- The library has the option to print warnings. At the moment, there is one \n  check that may lead to a warning: when telegram has a payload size of 5; \n  older OSP nodes do not support that size, they don't forward those telegrams.\n  Warnings are managed with `aospi_warnings_set(ena)` and `aospi_warnings_get()`.\n\n- The last sent and receive telegrams are available through `aospi_tx_last()` \n  and `aospi_rx_last()`. This enables commands like `osp fields tx`.\n\n- The library allows to set the idle time between telegrams; the `aospi_tx(...)`\n  inserts this after sending a telegram. Use `aospi_idletime_us_set()` and\n  `aospi_idletime_us_get()`. Default is 8 us, as specified by OSP.\n  \n- For statistics, the library keeps track of the number of telegrams sent \n  (with `aospi_tx()` and `aospi_txrx()`) or received (with `aospi_txrx()`).\n  These counters can be retrieved with `aospi_txcount_get()` respectively `aospi_rxcount_get()`.\n  These counter can be reset to 0 with `aospi_txcount_reset()` and `aospi_rxcount_reset()`.\n\n- For measurements, two functions are provided. The function `aospi_txrx_us()`\n  returns the round trip time of the last `aospi_txrx()`. The function \n  `aospi_txrx_hops(...)` returns an estimate of the number of hops (telegram \n  forwards by intermediate nodes) that were needed by the last `aospi_txrx()`.\n  \n- To test the (OSP32) PCB the following functions are provided. The first pair\n  `aospi_outoena_set(...)`/`aospi_outoena_get()` allows testing the control line of \n  the output enable of the outgoing level shifter, the second pair \n  `aospi_inoena_set(...)`/`aospi_inoena_get()` allows testing the control line of \n  the output enable of the incoming level shifter. Do not use during regular\n  operation.\n\n- `aospi_init()` must be called before using any of the above functions.\n  This configures all pins, interrupt routine, SPI master and SPI slave \n  drivers and puts the mux to BiDir. This function has a parameter `phy` \n  (of type `aospi_phy_t`) that defaults to `aospi_phy_mcub`, initializing the \n  driver for the physical layer implemented on the OSP32 board: MCU mode \n  type B; see [Physical layer](#physicallayer) for details.\n  \n  Function `aospi_phy_t aospi_phy_get()` returns the physical layer selected \n  with init.\n\n- The macro `AOSPI_TELE_MAXSIZE` defines the maximum size of an OSP telegram.\n\n- Finally, there is the macro `AOSPI_VERSION`, which identifies the version of the library.\n\n\n## Telegram dissector\n\nThis library comes with a small Python program that dissects \na byte array into telegram fields, checks CRC and pretty prints\nall results. See [telegram](python/telegram) for instructions. \n\nThis is an example of a pretty printed telegram,\nin this case the reply for an initbidir of a chain of length 5.\n\n```\n(env) OSP_aospi\\python\\telegram\u003erun A0 15 02 6F 50 30\n          +---------------+---------------+---------------+---------------+---------------+---------------+\nbyteval   |      A0       |      15       |      02       |      6F       |      50       |      30       |\nbyteix    |0 0 0 0 0 0 0 0|1 1 1 1 1 1 1 1|2 2 2 2 2 2 2 2|3 3 3 3 3 3 3 3|4 4 4 4 4 4 4 4|5 5 5 5 5 5 5 5|\nbitix     |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|\nbitval    |1 0 1 0 0 0 0 0|0 0 0 1 0 1 0 1|0 0 0 0 0 0 1 0|0 1 1 0 1 1 1 1|0 1 0 1 0 0 0 0|0 0 1 1 0 0 0 0|\n          +-------+-------+-----------+---+-+-------------+-------------------------------+---------------+\nfield     |preambl|      address      | psi |   command   |            payload            |      crc      |\nbin       | 1010  |    0000000101     | 010 |   0000010   |   01101111    :   01010000    |   00110000    |\nhex       |  0xA  |       0x005       | 0x2 |    0x02     |     0x6F      :     0x50      |   0x30 (ok)   |\nmeaning   |   -   |         5         |  2  |  initbidir  |      111      :      80       |    48 (ok)    |\n          +-------+-------------------+-----+-------------+-------------------------------+---------------+\n```\n\n\n## Board design\n\nThe _aospi_ library assumes presence of certain peripherals and connections.\n \n- An ESP32S3 is connected to two SAIDs.\n\n- Level shifters with output enable separate the SAIDs from the ESP.\n\n- A mux is implemented to select between the BiDir or Loop mode\n (by enabling the associated level shifter).\n\n- Several pull-up and pull-down resistors configure default line levels.\n\nAt the moment, the **OSP32** board implements all these assumptions.\n\n\n### High level schematic diagram\n\nThe following diagram sketches the assumed connections.\n\n![schematic](extras/schematic.drawio.png)\n\n\n### Explanation of the schematic\n\nThe communication between the ESP and OSP chain involves several lines.\n\nThere are three lines (top part of drawing) for mastering command telegrams: \nclock (SCLK), data (MOSI), and enable (OENA).\nThe SCLK and MOSI are the output lines of the SPI master in the ESP.\nThe OENA is a GPIO of the ESP, which enables the output of the level shifter\nwhen telegrams are being send over SCLK/MOSI.\n\n\u003e Sending command telegrams (TX) uses the so-called _OUT_ pipe (perspective of the ESP).\n\nThere are in total seven lines (bottom part of drawing) for receiving \nresponse telegrams: clock (SCLK), data (MOSI), enable (ONEA); and also \none tap to monitor the clock (CINT) - this used to be done with an \ninterrupt, hence the now bad name.\n\nFurthermore the SPI slave in the ESP needs as input a select line (SSEL), \nand since the OSP node doesn't provide that, the ESP generates it (MSEL).\nFinally, there is a GPIO line (DIRL) that controls the BiDir or Loop mode -\nit controls whether the IN.BIDIR or the IN.LOOP level shifter has its\noutput enabled (towards the ESP) when OENA asserts. \nThis implements a _direction mux_.\n\n\u003e Receiving response telegrams (RX) uses the so-called _IN_ pipe (perspective of the ESP).\n\n\n### Issues\n\nSome issues controlling OSP nodes deserve above normal attention. \nThey are discussed in various sections of this document.\n\n- RESET issue\n- level shifter pull-ups issue\n- missing SSEL issue\n- dual master issue\n- different modes issue\n- fixed clock issue\n- mux issue\n\n\n### Level shifter for sending\n\nOn the OSP32 board there is a level shifter between the MCU and the first \nSAID, dedicated to sending. This level shifter has several roles.\n\n- As the name indicates, it shifts the \n  3V3 SPI lines of the MCU to the \n  5V SION and SIOP lines of the first OSP node.\n  \n- The level shifter, has an output disable on the OSP side.\n  The MCU sets the output always to tri-state (not connected) except while\n  sending a telegram. This is important, because in BiDir mode, the first \n  OSP node can also master a response telegram. If this happens at the same \n  moment the MCU is mastering a command telegram, then two masters would drive\n  the lines, giving the possibility of a shortcut. This is referred to as the\n  _dual master issue_.\n  \n- The output disable also solves the _RESET issue_.\n  When the MCU sends a RESET telegram to the first node, the node will also \n  reset its comms mode (MCU, EOL, LVDS, CAN). To select which of the four\n  modes to use the node will look at the SIO line levels and these should \n  indicate MCU (high for SIO.P and low SIO.N). \n  \n  If not, the OSP node will select another comms mode (e.g. LVDS) and from \n  that moment it is no longer possible to send telegrams from the MCU to \n  the OSP node. It is effectively locked, until a power cycle.\n  \n  The MCU must ensure the correct N and P line levels after mastering \n  the RESET telegram (not the N and P levels that the SPI master happens\n  to leave the SCLK and MOSI lines in). An easy way to do that is to \n  disconnect the SPI block from the node by using the output disable of \n  the level shifter.\n\nThe level shifter used on the OSP32 board is unidirectional, \nbut that is not very relevant.\n\nIf the MCU has 5V SPI lines, level shifting is not needed. However, the \nsecond (_dual masters_) and third bullet (_RESET issue_) still need to be \ncovered. Both could be covered with a buffer. Another option is to \nreconfigure the MCU pins after an SPI transaction, switching them to \ntri-state.\n\n**Warning:** some level shifters have internal pull-ups. This leads to \na problem related to the _RESET issue_, the _level shifter pull-ups issue_.\nIf the level shifter has internal pull-ups on the lines that connect to\nthe SIO port, then, after a power on, the port will be configured for \nCAN mode, effectively blocking the node from receiving any message.\n\n\n### Two level shifters for receiving\n\nOn the OSP32 board there are _two_ level shifters between the MCU and the \nOSP chain dedicated for reception: one for the first node (in case of BiDir \nsetup) and one for the last node (in case of Loop setup). \nLike the \"send level shifter\", these two level shifter have several roles.\n\n- As the name indicates, they shift the 5V SION and SIOP lines of the first \n  or last OSP node to the 3V3 SPI lines of the MCU.\n\n- The level shifters have an output disable on the MCU side.\n  Their outputs are wired together, but the _selector_ block only enables \n  one of them at a time solving the _mux issue_. The three together \n  implement an input mux under control of the MCU. It selects between \n  BiDir responses (from the first OSP node) or Loop responses (from the last \n  OSP node).\n  \n  ![Mux](extras/levelshift.drawio.png)\n\n- The MCU sets the outputs always to tri-state (output disable) except while\n  receiving a telegram. This helps, in BiDir mode, to not receive the command \n  telegram which is send. \n\n- The level shifters are unidirectional. This solves the _RESET issue_ \n  (see above) for reception side.\n\n  **Warning:** some (bidirectional) level shifters have pull-ups on both \n  sides. If such a shifter were used on the reception side, the _RESET issue_ \n  pops up here as well: when the last node receives a RESET, it would see \n  both SIO lines towards the MCU being pulled up, making the node reconfigure \n  that SIO port to CAN instead of EOL.\n\n- On the OSP32 board, the _selector_ block is implemented with a quad NOR.\n  It ensures that at no time both level shifters have their output enabled.\n\nReal-life applications (as opposed to the evaluation kit the OSP32 is) \ntypically chose either BiDir or Loop but not both. As a result, the \n_selector_ and one level shifter can be removed.\n\nOn top of that, if the MCU has 5V SPI lines, the (last) level shifter is also \nnot needed. However, the _RESET issue_ must be solved (but probably having \nthe MOSI and SCLK line as inputs is sufficient).\n\n\n### No level shifters - 5V MCU\n\nWhen the MCU is 5V, there is no reason to have level shifters.\nHowever, the _RESET issue_ and  _dual master issue_ still need to be solved.\nThis could be solved by replacing the OUT level shifter by an external buffer \nwith tri-state output. If that one is controlled via the OENA line, there is \nno need to change the library. The IN (either from BiDir or Loop) is 5V \n_input_ so no need for a buffer. \n\nIf the IN also needs the MUX (support for both BiDir and Loop), the two \nIN level shifters could be replaced by two external buffers with tri-state \noutput (or some other switch). Again, if they are controlled via the \nOENA.BIDIR and OENA.LOOP lines, there is no need to change the library.\n\nIt is even possible, if the MCU supports those capabilities, to absorb the \ntri-stating in the MCU, by configuring the OUT pads as tri-state instead of \nsetting OUT OENA low. This would require a change in the library.\n\nAlso the MUX could be implemented by an MCU if it has an internal pin MUX. \nAlso this would require a change in the software, re-configuring pins when \nthe current firmware changes DIRL.\n\nThe diagram below shows the various connection possibilities, upper left \n(green) the OSP32 board.\n\n![Connections](extras/connections.drawio.png)\n\nThe left-most column assumes a 3V3 MCU, and thus uses level shifters.\nThe three architectures in this column are supported by the aospi lib.\n\nThe center column assume a 5V MCU, so no level shifters are needed.\nHowever due to the _RESET issue_ and _dual master issue_, a buffer with tri-state \nis needed. For BiDir and Loop only rows, no mux is needed, so the IN buffers \ncan be avoided in the lower two rows. The three architectures in the center \ncolumn are supported by the aospi lib.\n\nThe right-most column assumes a 5V MCU, so no level shifters are needed.\nThis column assumes an MCU that can dynamically re-configure its (OUT) pads \nto tri-state, and can dynamically re-route (IN) pads to internal peripherals \n(thus implementing a pin MUX). The ESP32S3 on the OSP32 board has these \nfeatures (but it is 3V3). The three architectures in the right-most column \nrequire changes to the aospi lib: replacing control of the output enable \nlines (red) or BiDir/Loop select line (blue) with pad-configuration or \npad-reroute instructions specific for that MCU.\n\nThe two options in the lower right (green) would be typical production board \nimplementations (no extraneous components, like on the evaluation board)\n\n\n## Module architecture\n\nESP32S3 integrates 4 SPI peripherals. \nSPI0 and SPI1 are used internally to access the ESP's attached flash memory.\nThis library uses SPI2/HSPI for mastering and SPI3/FSPI for slaving.\n\nThe SPI _master_ driver comes with the ESP tool chain. The SPI _slave_ driver \ncomes from [Hideaki Tai](https://github.com/hideakitai/ESP32SPISlave).\nThe _aospi_ library comes with a [copy](src/slave) of ESP32SPISlave included, \nto ensure there are no versioning issues. The imported library is actually \na single h-file (with code).\n\n![Modules](extras/aospi-modules.drawio.png)\n\nThe `aospi.cpp` wraps the master and slave driver, with code to handle the \nlevel shifters, selector, slave select, etc.\n\n\n## Execution architecture\n\nSending command telegrams using 2-wire SPI is relatively straightforward\nusing the standard ESP SPI master driver.\n\nReceiving response telegrams is harder, even with Hideaki Tai's driver.\nThis is mainly due to the stringent timing requirements.\nMost tough scenario is getting a response from the first node in BiDir mode.\nThe ESP has 5us to switch from mastering to slaving.\n\nThis chapter explains the execution architecture in more detail.\n\n\n### Sending command telegrams\n\n- The MCU sends (command) telegrams to the first node of the OSP chain \n  (SAID OUT on OSP32 board). The MCU typically uses an SPI _master_ block \n  for that.\n  \n- The first node must always be configured in _MCU mode_ \n  (pull-up on SIO.P and pull-down on SIO.N).\n\n- The communication between MCU and first node is either \n  _1-wire SPI_ (aka type A) or _2-wire SPI_ (aka type B).\n\n  - 1-wire SPI only uses the P wire. The signal contains clock and data by \n    using so-called _Manchester_ encoding. This means that the MCU firmware \n    must \"double\" the telegram bits before it passes the telegram to the \n    SPI master block.\n    \n  - 2-wire SPI uses the P wire for data and the N wire for clock.\n    This means that the MCU firmware can just pass the telegram to \n    the SPI master.\n    \n  - The selection between 1-wire SPI and 2-wire SPI is made by an OTP bit \n    in the SAID (bit 0D.3 `SPI_mode`).\n    \n  - The SAID AS1163 has this OTP bit, the OSIRE RGBi E3731i only supports \n    1-wire SPI.\n  \n\n### Receiving response telegrams\n\n- The OSP chain sends (response) telegrams back to the MCU.\n  The MCU typically uses an SPI _slave_ block for that.\n  \n- A configuration choice has to be made: \n  either the first or the last node of the chain sends back the responses.\n  \n  - If the first node is chosen, this is known as bi-directional (_BiDir_) \n    communication.\n  \n    ![BiDir](extras/dirbidir.drawio.png)\n    \n  - If the last node is chosen, this is known as uni-directional (_Loop_) \n    communication.\n  \n    ![Loop](extras/dirloop.drawio.png)\n    \n  - The OSP32 board and this library support both modes.\n  \n- In either case, the last node must be configured for _EOL mode_\n  (pull-down on SIO.P and pull-up on SIO.N).\n  \n   - In Bidir, a _terminator_ (which has the two resistors) must be attached \n     to the last node. Secondly, the first node must be connected to the MCU \n     slave block.\n  \n   - In Loop, a _wire_ connects the last node to the MCU slave block \n     (SAID IN on OSP32 board) and it must be configured for _EOL mode_ \n     (pull-down on SIO.P and pull-up on SIO.N).\n  \n- On the OSP32 board, the first node is connected to the SPI slave, and the\n  last node is connected to the SPI slave, and a mux selects the active \n  connection.\n  \n  ![BiDir](extras/dirosp32.drawio.png)\n\n- In either case, and independent of the send mode, reception uses two wires:\n  P for data and N for clock.\n\n\n### Physical layer\n\nThe OSP32 board and this _aospi_ library are developed for the following \nscenario: MCU using 2-wire SPI towards the first SAID. This physical layer \nis also known as MCU mode type B. However, the difference with the physical \nlayer 1-wire Manchester (also known as MCU mode type A) is small. \nFor software the differences are:\n\n- Every 1 bit must be encoded as a low-high transition and every \n  0 bit as a high-low transition.\n- The clock frequency must be doubled to keep the same bit rate.\n\nBy calling `aospi_init()`, the argument `phy` which has a default value \n`aospi_phy_mcub`, ensures that the lib is configured for MCU mode type B. \nHowever by passing explicitly `aospi_phy_mcua`, the lib is configured for \nMCU mode type A. This basically takes care of the bit doubling and \nfrequency doubling.\n\nHowever, the N and P lines of the MCU, by default run to the SAID OUT, \nwhose OTP is burned for type B. So SAID OUT can not be used with \n`aospi_init(aospi_phy_mcua)`. The OSP32 board needs to bypass SAID OUT.\nSee the documentation coming with example [aospi_mcua](examples/aospi_mcua/readme.md)\nfor how to achieve that.\n\n\n### Telegram timing\n\n- Below diagram is the timing of one telegram to one node.\n  The diagram focuses on the OSP node that receives the command,\n  executes the command and (optionally) gives a response.\n\n  ![Timing node](extras/telegram-timing.drawio.png)\n\n- The resulting system trip timing, not in one node but in a chain, is \n  depicted below. Here we also look at the nodes before and after the \n  executing node.\n\n  ![Timing chain](extras/timing.drawio.png)\n  \n- We have temporarily modified `aospi.cpp` to pulse OENA to flag\n  the moments the round trip timing measurements are made in the driver.\n  \n  This allows us to validate the theoretical timings with reality.\n  There is a bit of time delay introduced by software \n  (`t_swpre` and `t_swpost`), but the match is quite accurate, see below trace.\n  \n  ![OSP32overview](extras/roundtriptiming.png)\n\n\n## Implementation notes\n\nHere is an overview of the time scales\n\n![Time scale](extras/timescale.drawio.png)\n\n- The _aospi_ library is hardwired to use specific pins of the ESP32S3.\n  The library could be used with other pins of the ESP32S3, and maybe even \n  with other ESP32xx MCUs. In this case the macros that identify pins \n  (like `#define AOSPI_OUT_SCLK  4`) have to be changed. See \n  [aospi.cpp](src/aospi.cpp) for those macros, and refer to the high level \n  schematic diagram.\n\n- To send a command telegram (`aospi_tx()`), the SPI master driver on the \n  ESP is used. It follows the Arduino documentation.\n  \n  ```C++\n  spi.beginTransaction(...);\n    digitalWrite(SSEL, ACTIVE);\n    spi.transferBytes( txbuf, rxbuf, size );\n    digitalWrite(SSEL, INACTIVE); \n  spi.endTransaction();\n  ```\n  \n  The _aospi_ implementation (`aospi_tx()`), replaces the SSEL with OENA to \n  enable the output of the level shifter. \n  This is relevant for the _RESET issue_ and the _dual master issue_ (see above).\n\n  One timing requirement that is not free in OSP is on SCLK (in plain SPI, \n  the master can vary the clock as long as it is not too fast).\n  The clock must be 2.4MHz and no delays are allowed e.g. between bytes \n  (_fixed clock issue_). The reason is that OSP has no SSEL, \n  so a dead time of about two clocks signals end-of-frame.\n  \n  Another timing requirement is on RESET. \n  All commands execute faster than they can be forwarded, so multiple \n  writes cause no problems (ignoring writes that trigger a response).\n  There are a couple of exceptions to this rule; one is the RESET command.\n  A node forwards the RESET to its successor (in the usual fast way of 7 us),\n  but then RESETs itself. This takes 150us, and during that time no\n  telegrams should be send to that node.\n  \n- The SPI slave driver from Hideaki Tai has a non-trivial API \n  especially for the most flexible use \"Non-blocking multiple transactions\".\n  The SPI slave drivers maintains a queue of transactions (byte buffers).\n  It is only possible to receive SPI messages when the queue is non-empty.\n  This is known as \"transactions are in flight\". \n  When an SPI message comes in, the driver takes a transaction (buffer) \n  from the queue, and uses that to store the incoming bytes. Then, \n  \"the transaction is completed\". See \n  [driver documentation](https://github.com/hideakitai/ESP32SPISlave?tab=readme-ov-file#non-blocking-multiple-transactions).\n\n- The SPI slave sees response telegrams either from the first node\n  (when using BiDir direction), or from the last node (Loop direction).\n  Recall that the first node has its SIO configured in MCU mode, with P\n  pulled up, and N pulled down. The last node has its SIO configured as\n  EOL, with P pulled down, and N pulled up.\n  \n  This is relevant, because when an OSP node acts a master (for a response), \n  its default clock line (N pad) follows this convention. In other words, \n  when an OSP node masters in MCU configuration (BiDir), it uses _SPI mode 0_: \n  clock (N pad) default **low**. When an OSP node masters in EOL configuration \n  (Loop), it uses _SPI mode 3_: clock (N pad) default **high**. This is known \n  as the _different modes issue_. In both cases data is captured by the SPI \n  slave in the MCU on the rising clock. The MOSI line (P pad) does not have \n  defaults in the SPI protocol.\n  \n  In OSP context, the SPI block is only receiving data (MOSI) it is\n  not sending data (MISO) back to the master (OSP node). Sending would\n  happen on the opposite clock edge (falling clock).\n  \n  Since the slave only needs to receive, it is only triggered by the\n  rising edges of the clock. As a result, the _ESP SPI slave driver can\n  receive both BiDir and Loop telegrams using both MODE0 or MODE3_.\n  \n  This is expected to hold for other MCUs as well but has not been checked.\n\n- To receive a response telegram, first a command telegram has to be send \n  (`aospi_txrx()`). This function is time critical. It consists of the \n  following steps.\n  \n  ```\n  queue a transaction in the underlying SPI slave driver\n  transmit the command telegram (OUT OENA hi, transfer bytes, OUT OENA lo)\n  connect ESP to OSP chain (IN OENA hi), enable reception (assert IN SSEL via MSEL)\n    wait for response (see below)\n  disable reception (deassert IN SSEL) and disconnect ESP (IN OENA lo)\n  pick up received bytes from SPI slave driver\n  ```  \n\n- Note that a couple of actions need to be taken after sending and before \n  receiving. Among that are three GPIO writes (OUT OENA, IN OENA, MSEL).\n  In order not to lose time (OSP gives 5 us for the switch over), \n  Arduino's `digitalWrite()` has been replaced with direct SFR (special \n  function register) access (eg `AOSPI_IN_OENA_SET()`).\n\n- Next to the short handover time, there is another issue: the \n  _missing SSEL issue_. OSP nodes do not have an outgoing SSEL line, and ESPs \n  cannot work without them (they need them to frame a message to know which \n  byte is the last). The OSP32 board has a loop back wire (from MSEL to SSEL).\n  Since a response comes as an answer to a command, there is a known moment \n  to assert MSEL.\n\n  However, the SPI slave driver does not have a call-back when it receives \n  bytes. If MSEL would de-assert early, the SPI driver would return a \n  transaction (byte buffer) of length 0.\n  \n  To solve this there is another line, a tap on SCLK to GPIO pin named CINT.\n  A busy wait checks for the first clock flip on CINT.\n  Since response telegram length is given (max 12 bytes or 96 clock ticks) \n  this tells when to de-assert MSEL once this first flip is detected.\n  \n  The `wait for response` (above) is implemented as follows.\n  \n  ```\n    wait for flip on CINT but no longer than 17.4ms (timeout)\n    wait num bytes in response *8 / 2.4MHz\n  ```\n  \n- The time out of 17.4 ms used in the code is based on the worst-case chain \n  (longest OSP chain in BiDir) and the worst-case command (I2C read of \n  8 bytes with slowest clock).\n\n\n## Traces\n\nTwo traces from a [logic analyzer](https://www.saleae.com/pages/downloads) \nare available with this documentation.\nThe high level schematic diagram indicates where the probes of the \nlogic analyzer are placed (colored circles).\n\n### BiDir mode\n\nA trace is made, while running `aospi_txrx` in BiDir mode.\nThe trace is also part of the documentation: [aospi-txrx.bidir.sal](extras/aospi-txrx.bidir.sal).\n\n![bidir mode, trace all](extras/aospi-txrx.bidir.all-trace.png)\n\n- The purple pulses (D7, OUT.MCU.OENA) indicates when the OUTlevel shifter is enabled, \n  i.e. when a command telegram is send. The above screenshot shows four: RESET, INITBIDIR, CLRERROR, and GOACTIVE.\n- The blue and green lines show data (D5 OUT.SAID.MOSI) and clock (D5 OUT.SAID.SCLK) of the first node.\n  See below for a zoom in.\n- The orange and red lines show data (D3 IN.SAID.MOSI) and clock (D2 IN.SAID.SCLK) of the last node.\n- Observe that after the RESET telegram both the clock of the first and the data of the last node show a pulse.\n  This is part of the reset cycle of those nodes, establishing their new comms mode.\n- Observer that after the second pulse (INITBIDIR), the first node starts mastering the response.\n- The brown and white lines show data (D1 IN.MCU.MOSI) and clock (D0 IN.MCU.SCLK) of the slave block.\n- Observe that the slave sees the response.\n\nThe figure below shows details of the INITBIDIR command ans response. \n\n![bidir mode, trace BIDIR](extras/aospi-txrx.bidir.bidir-trace.png)\n\n- The BIDIR command telegram being send is sliced by the logic analyzer as A0 04 02 A9; \n  this matches the source code.\n- The response telegram is sliced as A0 09 02 00 50 6D, \n  which shows that the last node has address 2 in response to a INITBIDIR.\n  ![](extras/aospi-txrx.bidir.bidir-slice.png)\n\nThe figure below shows details of the INITBIDIR command ans response. \n\n![bidir mode, trace BIDIR](extras/aospi-txrx.bidir.identify-trace.png)\n\n- Observe the tight timing (5 us) between command and response.\n- The response telegram is sliced as A0 06 07 00 00 00 40 AA, the 00000040 is the type for SAID.\n- Observer that the default clock line of the response is **low**.\n\n\n### Loop mode\n\nA trace is made, while running `aospi_txrx` in Loop mode.\nThe trace is also part of the documentation: [aospi-txrx.loop.sal](extras/aospi-txrx.loop.sal).\n\n![loop mode, trace all](extras/aospi-txrx.loop.all-trace.png)\n\n- We see the same pulses after the RESET telegram.\n- Observer that after the second pulse (INITLOOP), the last node starts mastering the response.\n  We see that on the orange and red lines (D3 IN.SAID.MOSI and D2 IN.SAID.SCLK).\n- The brown and white lines show what the SPI slave sees.\n\nThe figure below shows details of the INITLOOP command and response. \n\n![bidir mode, trace BIDIR](extras/aospi-txrx.loop.loop-trace.png)\n\n- The LOOP command telegram being send is sliced by the logic analyzer as A0 04 03 86; \n  this matches the source code.\n- The response telegram is sliced as A0 09 03 00 50 63, \n  which shows that the last node has address 2 in response to a INITBIDIR.\n\n  ![](extras/aospi-txrx.loop.loop-slice.png)\n\n- Observer that the default clock line of the response is **high**.\n\n\n## Version history _aospi_\n\n- **2026 April 21, 2.0.0**\n  - Added `aospi_phy_t aospi_phy_get()` used in `aocmd_version_main()`.\n  - Added `aospi_tx_last()` and `aospi_rx_last()` for the `osp fields` command.\n  - Added timescale [overview](extras/timescale.drawio.png).\n  - Improved `aospi_txrx_us()` and `aospi_txrx_hops()` documentation.\n  - Added 8µs idle time between telegrams; default can be changed with `aospi_idletime_us_set()`.\n  - Improved report of `aospi_time.ino`.\n  - Added printing warnings for telegrams with PSI of 5 (since not supported by RGBI); use `aospi_warnings_set()` and `aospi_warnings_get()`.\n  - Fixed bug in Telegram dissector (`python\\telegram`): removed duplicate `setpwm`/`setpwmchn`.\n\n- **2025 September 16, 1.0.1**\n  - Textual corrections in multiple examples.\n  - Added link to examples.\n  - Debug feature: zap unreliable bytes in reception buffer to 00.\n\n- **2025 May 21, 1.0.0**\n  - `aospi_init()` can now be configured for physical layer type A (was only type B).\n  - Example `aospi_mcua.ino` added to show how to use physical layer type A (with reconfigure documentation).\n  - Added OSP32 v12 names for LEDs (e.g. L1.0 aka OUT0).\n  - The SPI _slave_ driver upgraded to v0.6.8 to make it compile with esp32 board lib 3.2.0.\n  \n- **2025 April 22, 0.5.9**\n  - The SPI _slave_ driver is patched to make it compile with esp32 board lib 3.2.0.\n\n- **2025 February 21, 0.5.8**\n  - The SPI _slave_ driver upgraded to v0.6.5 to make it compile with esp32 board lib 3.1.1.\n  - Added USE mode note to `aospi_bringup.ino`.\n  - Text corrections in `python\\telegram\\readme.md` and `examples\\aospi_bringup\\readme.md`.\n\n- **2024 November 29, 0.5.7**\n  - Added example `aospi_bringup.ino`.\n  - Text correction in `readme.md`; added _level shifter pull-ups issue_.\n\n- **2024 October 22, 0.5.6**\n  - Telegram dissector (`python\\telegram`) now shows casting mode.\n  - Replaced clock tapping mechanism in `aospi.cpp`; from ISR to polling (faster).\n\n- **2024 October 16, 0.5.5**\n  - Added telegram dissector with CRC computation (in Python).\n  \n- **2024 October 8, 0.5.4**\n  - `src/slave/*` line endings changed from LF to CR+LF.\n\n- **2024 October 7, 0.5.3**\n  - Prefixed `modules.drawio.png` with library short name.\n  - Moved domain from `github.com/ams-OSRAM-Group` to `github.com/ams-OSRAM`.\n\n- **2024 September 10, 0.5.2**\n  - Changes in `readme.md`.\n  - Fixed Bug in `aospi_txrx`; function `aospi_dirmux_set_loop()` no longer needs parameter.\n  - Added BEHAVIOR section to explanation in examples.\n  \n- **2024 September 5, 0.5.1**\n  - API section in readme now shows parameter names.\n  - Text updates in `readme.md`.\n  - Text update in documentation of `aospi_txrx_hops()`.\n\n- **2024 August 28, 0.5.0**\n  - Added new api functions `aospi_txrx_us()` and `aospi_txrx_hops()`.\n  - Added example `aospi_time.ino`, using the new api functions.\n  - Added round trip timing trace picture.\n  - Added links in `readme.md` for all example sketches.\n  - Extended \"System overview\" (and detailed the image).\n  - Fixed warning on `printf` format specifier in `aospi_txrx.ino`.\n\n- **2024 August 9, 0.4.2**\n  - Added \"System overview\" to `readme.md`.\n  - Small update to `dirosp32.drawio.png`.\n\n- **2024 August 5, 0.4.1**\n  - Corrected landing page link in readme.md from aotop to OSP_aotop.\n  - Updated the two timing diagrams (telegram and chain).\n  - Remove \"oalib\" from `sentence=` in `library.properties`.\n  - Check with `AORESULT_ASSERT(aospi_inited)` in `aospi.cpp` test getters \u0026 setters.\n  - Tab2spaces in `aospi.cpp`.\n  - Text updates in `readme.md`.\n  - Fixed typos `@parm` to `@param`\n\n- **2024 July 7, 0.4.0**  \n  - Arduino name changed from `OSP 2-wire SPI - aospi` to `OSP 2wireSPI aospi`.\n  - Added link for traces.\n  - Added blue slave line in BiDir diagram (`dirbidir.drawio.png`) and green dashed OSP32 border line in OSP32 diagram (`dirosp32.drawio.png`).\n  - Renamed dir `extra` to `extras`.\n  - Small corrections in readme.md.\n  - Added (for board testing) `aospi_outoena_set()`/`aospi_outoena_get()` and `aospi_inoena_set()`/`aospi_inoena_get()`.\n\n- **2024 July 3, 0.3.1**  \n  - `license.txt`, `aospi_tx.ino`, `aospi_txrx.ino` line endings changed from LF to CR+LF.\n\n- **2024 July 2, 0.3.0**  \n  - Initial release candidate.\n\n\n(end)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fams-osram%2Fosp_aospi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fams-osram%2Fosp_aospi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fams-osram%2Fosp_aospi/lists"}