{"id":25343956,"url":"https://github.com/ssilverman/teensydmx","last_synced_at":"2025-08-31T13:39:25.368Z","repository":{"id":37431797,"uuid":"85892375","full_name":"ssilverman/TeensyDMX","owner":"ssilverman","description":"A full-featured DMX library for Teensy 3, Teensy LC, and Teensy 4. \"Programmable DMX and arbitrary USB serial device emulation.\"","archived":false,"fork":false,"pushed_at":"2023-06-27T15:15:23.000Z","size":887,"stargazers_count":101,"open_issues_count":1,"forks_count":6,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-04-05T04:31:43.867Z","etag":null,"topics":["dmx","dmx-library","dmx512","rdm","rdm-responder","teensy","teensy-lc","teensy3","teensy32","teensy35","teensy36","teensy3x","teensy4","teensy40","teensylc"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause-clear","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssilverman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","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},"funding":{"github":"ssilverman"}},"created_at":"2017-03-23T01:15:28.000Z","updated_at":"2025-04-03T12:46:44.000Z","dependencies_parsed_at":"2025-02-14T11:00:13.607Z","dependency_job_id":"64c02b0b-6598-45a3-9785-baee1a3dd3c8","html_url":"https://github.com/ssilverman/TeensyDMX","commit_stats":null,"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"purl":"pkg:github/ssilverman/TeensyDMX","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FTeensyDMX","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FTeensyDMX/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FTeensyDMX/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FTeensyDMX/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssilverman","download_url":"https://codeload.github.com/ssilverman/TeensyDMX/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssilverman%2FTeensyDMX/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272988782,"owners_count":25026959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dmx","dmx-library","dmx512","rdm","rdm-responder","teensy","teensy-lc","teensy3","teensy32","teensy35","teensy36","teensy3x","teensy4","teensy40","teensylc"],"created_at":"2025-02-14T11:00:03.545Z","updated_at":"2025-08-31T13:39:25.347Z","avatar_url":"https://github.com/ssilverman.png","language":"C++","funding_links":["https://github.com/sponsors/ssilverman","https://www.buymeacoffee.com/ssilverman"],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://www.buymeacoffee.com/ssilverman\" title=\"Donate to this project using Buy Me A Coffee\"\u003e\u003cimg src=\"https://img.shields.io/badge/buy%20me%20a%20coffee-donate-orange.svg?logo=buy-me-a-coffee\u0026logoColor=FFDD00\" alt=\"Buy Me A Coffee donate button\"\u003e\u003c/a\u003e\n\n# Readme for TeensyDMX v4.3.0-snapshot\n\nThis is a full-featured library for receiving and transmitting DMX on Teensy 3,\nTeensy LC, and Teensy 4. It follows the\n[ANSI E1.11 DMX512-A specification](https://tsp.esta.org/tsp/documents/published_docs.php).\n\n## Table of contents\n\n1. [Features](#features)\n   1. [Receiver timing limitations and RX line monitoring](#receiver-timing-limitations-and-rx-line-monitoring)\n   2. [Transmitter timing limitations](#transmitter-timing-limitations)\n2. [The TODO list](#the-todo-list)\n3. [How to use](#how-to-use)\n   1. [Examples](#examples)\n   2. [Synchronous vs. asynchronous operation](#synchronous-vs-asynchronous-operation)\n4. [DMX receive](#dmx-receive)\n   1. [Code example](#code-example)\n   2. [Retrieving 16-bit values](#retrieving-16-bit-values)\n   3. [Error counts and disconnection](#error-counts-and-disconnection)\n      1. [The truth about connection detection](#the-truth-about-connection-detection)\n      2. [Keeping short packets](#keeping-short-packets)\n   4. [Packet statistics](#packet-statistics)\n   5. [Error statistics](#error-statistics)\n   6. [Synchronous operation by using custom responders](#synchronous-operation-by-using-custom-responders)\n      1. [Responding](#responding)\n5. [DMX transmit](#dmx-transmit)\n   1. [Code example](#code-example-1)\n   2. [Packet size](#packet-size)\n   3. [Transmission rate](#transmission-rate)\n   4. [Synchronous operation by pausing and resuming](#synchronous-operation-by-pausing-and-resuming)\n   5. [Choosing BREAK and MAB times](#choosing-break-and-mab-times)\n      1. [Specific BREAK/MAB times](#specific-breakmab-times)\n         1. [A note on BREAK timing](#a-note-on-break-timing)\n         2. [A note on MAB timing](#a-note-on-mab-timing)\n      2. [BREAK/MAB times using serial parameters](#breakmab-times-using-serial-parameters)\n   6. [Inter-slot MARK time](#inter-slot-mark-time)\n   7. [MBB time](#mbb-time)\n   8. [Error handling in the API](#error-handling-in-the-api)\n6. [Technical notes](#technical-notes)\n   1. [Simultaneous transmit and receive](#simultaneous-transmit-and-receive)\n   2. [Transmission rate](#transmission-rate)\n   3. [Transmit/receive enable pins](#transmitreceive-enable-pins)\n   4. [Thread safety](#thread-safety)\n   5. [Dynamic memory allocation failures](#dynamic-memory-allocation-failures)\n   6. [Hardware connection](#hardware-connection)\n   7. [`Receiver` and driving the TX pin](#receiver-and-driving-the-tx-pin)\n   8. [Potential PIT timer conflicts](#potential-pit-timer-conflicts)\n7. [Code style](#code-style)\n8. [References](#references)\n9. [Acknowledgements](#acknowledgements)\n\n## Features\n\nSome notable features of this library:\n\n1. Teensy's default serial buffer isn't used; the data goes directly to/from\n   the DMX buffers from/to the UART ISRs. In other words, the library is\n   asynchronous and runs independently; all you need to worry about is setting\n   and getting channel data.\n2. Simple API: After setup, there's only two read calls (`readPacket` and `get`)\n   and two forms of one write call (`set` for single and multiple channels).\n3. The library properly handles DMX packets containing less than 513 slots.\n4. The transmitter refresh rate can be changed to something less than\n   \"maximum rate\".\n5. The transmitter can be paused and resumed to allow for packets that must be\n   adjacent to other packets. In other words, the asynchronous transmitter can\n   be used synchronously. For example, System Information Packets (SIP) require\n   this. See Annex D5 of ANSI E1.11.\n6. The transmitter timing parameters can be specified: BREAK, MAB, inter-slot\n   MARK time, and MBB.\n7. The receiver checks for timeouts according to the DMX specification. It\n   knows of the concept of being disconnected from a DMX transmitter when\n   timeouts or bad BREAKs are encountered in the data stream.\n8. Packet and error statistics are available. These can be used to detect\n   protocol problems, including timeouts, framing errors and bad BREAKs, short\n   packets (those less than 1196us), and long packets (those that exceed\n   513 bytes).\n9. The receiver can be used synchronously through the use of the `Responder`\n   API. Alternate start codes can not only be handled, for example, for Text\n   Packets or System Information Packets (SIP), but responses can be sent back\n   to the transmitter, for example for RDM.\n10. Functions for handling 16-bit data.\n\n### Receiver timing limitations and RX line monitoring\n\nWhen the RX line is not being monitored, there are limitations in the handling\nof received DMX frame timing. For BREAK and Mark after BREAK (MAB) times, only\nthe following cases are checked and not accepted as a valid DMX frame start:\n\n1. BREAK duration \u0026lt; ~44us.\n2. BREAK duration + MAB duration \u0026lt; ~96us.\n3. BREAK \u0026lt; ~88us and MAB \u0026ge; ~44us.\n\nThe following case is accepted as a valid frame start, even though it isn't\ncompliant with the DMX specification:\n\n1. BREAK duration \u0026gt; ~52us and MAB duration \u0026lt; ~44us.\n\nFor example, if a BREAK comes in having a duration of 53us and then a MAB comes\nin having a duration of 43us, their sum is 96us, and so the packet will be\naccepted. More generally, this will allow BREAKs that are shorter than the\nminimum required 88us if the MAB is shorter than 44us.\n\nThis limitation does not exist if the RX line is monitored. To monitor the line,\nconnect it to a digital I/O-capable pin and call `setRXWatchPin` with the pin\nnumber. The pin cannot be the same as the RX pin.\n\n### Transmitter timing limitations\n\nThe transmitter uses a UART to control all the output. On the Teensy, the UART\nruns independently. This means that any specified timings won't necessarily be\nprecise. They will often be accurate to within one or maybe two bit times. An\neffort has been made, however, to make sure that transmitted timings are at\nleast as long as the requested timings.\n\nFor example, if a 100us MAB is requested, the actual MAB may be 102 or 103us. If\na 40us inter-slot time is requested, the actual time may be 44us. And so on.\n\nAdditionally, certain timings will have a minimum length that can't be\nshortened, for example the MBB. This is simply due to code execution time\ninteracting with the interrupt and UART subsystems. For example, on a Teensy LC,\nthe minimum MBB is about 119us, even if the requested value is 0us.\n\n## The TODO list\n\nThese are either in the works or ideas for subsequent versions:\n\n1. Asynchronous responder data. Currently, the data is sent synchronously inside\n   the UART ISR where responders process the packet.\n2. Better MAB transmit timing, perhaps by somehow synchronizing with the baud\n   rate clock.\n3. Explore much more precise transmitter timings by not using the UART.\n\n## How to use\n\nThe classes you'll need are in the `qindesign::teensydmx` namespace: `Receiver`\nand `Sender`.\n\nAll class documentation can be found in `src/TeensyDMX.h`.\n\n### Examples\n\n`Receiver` examples:\n* `BasicReceive`: A basic receive example\n* `Flasher`: Change the flash speed of the board LED based on DMX input\n\n`Sender` examples:\n* `BasicSend`: A basic send example\n* `Chaser`: Chases values across all channels\n* `SendADC`: Sends the value from an ADC over a DMX channel\n* `SendTestPackets`: Sends test packets (start code 55h)\n\nExamples that show how to utilize synchronous and asynchronous transmission:\n* `SIPSenderAsync`: Sends SIP packets asynchronously\n* `SIPSenderSync`: Sends SIP packets synchronously\n\nExamples that show how to use a synchronous packet handler in a receiver:\n* `SIPHandler`: Understands System Information Packets (SIP) (start code CFh)\n* `TextPacketHandler`: Understands text packets (start codes 17h and 90h)\n\nTransmitter timing examples:\n* `RegenerateDMX`: Regenerates received DMX onto a different serial port and\n  with different timings\n\nOther examples:\n* `FastLEDController`: Demonstrates DMX pixel output using FastLED\n\nA more complex example showing how to behave as a DMX USB Pro Widget is\nin `USBProWidget`.\n\n### Synchronous vs. asynchronous operation\n\nBoth transmission and reception operate asynchronously. This means that\nthere's potentially a continuous stream of data being sent or received in\nthe background.\n\nThe transmitter will keep sending the same data until it's changed externally\nusing one of the `Sender::set` functions. Similarly, `Receiver::readPacket` and\n`Receiver::get` will return only the latest data; if more than a small amount of\ntime elapses between calls, then the data may have been changed more than once.\n\nBoth `Sender` and `Receiver` have a mode where they can operate synchronously.\nWith `Sender`, the stream can be paused and resumed, and packets inserted at\nappropriate spots. Similarly, `Receiver` can send received packets as they\narrive to start code-specific instances of `Responder`.\n\n## DMX receive\n\n### Code example\n\nFirst, create an object using one of the hardware serial ports, different from\nany serial ports being used for transmission:\n\n```c++\nnamespace teensydmx = ::qindesign::teensydmx;\n\nteensydmx::Receiver dmxRx{Serial1};\n```\n\nBefore using the instance, start the serial port internals:\n\n```c++\ndmxRx.begin();\n```\n\nUsing your own buffer whose length is at least `len` bytes, check for a packet\nhaving arrived, and if it has, copy `len` values starting from channel\n`startChannel`:\n\n```c++\n// For this example, assume buf is at least len bytes long\nint read = dmxRx.readPacket(buf, startChannel, len);\n```\n\n`read` will contain the number of bytes read, -1 if no packet is available, or\nzero if no values were read.\n\nNote that channel zero contains the start code, a special value, usually zero,\nthat occupies the first byte of the packet. The maximum DMX packet size is\n513, but may be smaller, depending on the system.\n\nEach call to `readPacket` is independent, meaning that if no packet has arrived\nafter a call to this function, subsequent calls will return -1.\n\n### Retrieving 16-bit values\n\nRetrieving 16-bit values is easy with one available 16-bit function:\n1. `get16Bit` for single words.\n\nThis works the same as the 8-bit `get` function, but uses the `uint16_t`\ntype instead.\n\n### Error counts and disconnection\n\nThe DMX receiver keeps track of three types of errors:\n\n1. Packet timeouts.\n2. Framing errors, including bad BREAKs.\n3. Short packets, i.e. those packets that occupy less than 1196us.\n\nThe counts can be retrieved via `errorStats()`. This returns an `ErrorStats`\nobject containing each of the error counts. These metrics are reset to zero when\nthe receiver is started or restarted.\n\nAn associated concept is _disconnection_. A receiver is considered _connected_\nwhen it is receiving valid DMX packets. When any timeouts occur, or when invalid\nBREAKs are detected, then the receiver considers itself _disconnected_. As soon\nas valid packets reappear, the receiver is once again _connected_.\n\nTo determine whether a receiver is connected, call its `connected()` function.\nA callback function, to be notified when this event happens, can also be set\nusing `onConnectChange`.\n\nThe following example sets the built-in LED according to the current connected\nstate. It uses a lambda expression, but a normal function could be used instead.\n\n```c++\ndmxRx.onConnectChange([](Receiver *r) {\n  digitalWriteFast(LED_BUILTIN, r.connected() ? HIGH : LOW);\n});\n```\n\n#### The truth about connection detection\n\nIn actual fact, the connection detection only works for most of the cases where\ntimeouts occur or when bad BREAKs happen. The one thing it can't do is detect\nwhen a BREAK condition occurs permanently, and, if none of the periodic\ninterrupt timers (PIT) are available, when an IDLE condition occurs permanently.\nUnless more data comes in, no further UART interrupts will be generated, so it\nis up to the user's code to detect this case.\n\nThe `Flasher` example contains an example of how to do this. In essence, the\n`readPacket` function will return a positive value if the desired data is\navailable in the last packet received. A timer can be reset when valid data is\nreceived, and then subsequent code can use the difference between the current\ntime and the timer value to determine if there's been a timeout.\n\nAn example that uses `elapsedMillis` to encapsulate the current time call:\n\n```c++\nconstexpr uint32_t kTimeout = 1000;  // In milliseconds\n\nelapsedMillis lastPacketTimer{0};\nuint8_t buf[250];\n\nvoid loop() {\n  int read = dmxRx.readPacket(buf, 0, 217);\n  if (read == 217) {  // Comparing to \u003e 0 means that _some_ data was received\n    // All the requested data was received\n    // Do stuff with it\n    lastPacketTimer = 0;\n  }\n\n  if (lastPacketTimer \u003c kTimeout) {\n    // Do work\n  } else {\n    // No connection\n  }\n}\n```\n\nA second technique would be to use the value returned from\n`lastPacketTimestamp()` as something that closely approximates the true latest\ntimestamp. For example:\n\n```c++\nconstexpr uint32_t kTimeout = 1000;  // In milliseconds\n\nvoid loop() {\n  if (millis() - dmxRx.lastPacketTimestamp() \u003e= kTimeout) {\n    // Respond to the timeout\n  }\n  // Do work\n}\n```\n\nIn actuality, a timer could be used to detect this condition, but it was chosen\nto not do this because it would use up a timer, add one extra timeout-specifying\nmethod, and the user code is probably good enough.\n\nIn summary, the _connected_ concept here has more to do with line noise and bad\ntiming than it does with a physical connection. Perhaps a future release will\nrename this API concept or address it with the timer...\n\n#### Keeping short packets\n\n_Short packets_ are nothing more than valid-looking packets that span a duration\nless than 1196us. The default is to discard them because they may indicate\nline noise.\n\nIt is possible to keep the data from these packets by enabling the \"Keep Short\nPackets\" feature using the `setKeepShortPackets` function. If these are kept\nthen the `PacketStats::isShort` variable will indicate whether the associated\npacket is a _short packet_.\n\nRecall that the `readPacket` function can atomically retrieve packet statistics\nassociated with the packet data. If `packetStats()` is used instead, then\nthere's no guarantee that the values will be associated with the most recent or\nnext packet data retrieval calls.\n\nNote that there is also an `isKeepShortPackets` function that can be polled for\nthe current state of this feature.\n\n### Packet statistics\n\nPacket statistics are tracked and the latest can be retrieved from a\n`PacketStats` object returned by `packetStats()`. Included are these variables:\n\n1. `size`: The latest received packet size.\n2. `isShort`: Whether the last packet was a _short packet_, a packet having a\n   duration less than 1196us. Keeping these packets can be enabled with the\n   `setKeepShortPackets` function.\n3. `timestamp`: The timestamp of the last packet received, in milliseconds. This\n   is set to the time the packet was recognized as a packet and not the end of\n   the last stop bit.\n4. `breakPlusMABTime`: The sum of the BREAK and MAB times, in microseconds. It's\n   not possible to determine where the BREAK ends and the MAB starts without\n   using another pin to watch the RX line. To set up a connected pin, see\n   `setRXWatchPin` and the section above on\n   [RX line monitoring](#receiver-timing-limitations-and-rx-line-monitoring).\n5. `breakToBreakTime`: The latest measured BREAK-to-BREAK time, in microseconds.\n   Note that this is not collected at the same time as the other variables and\n   only represents the last known duration. This will be out of sync with the\n   rest of the values in the presence of packet errors.\n6. `frameTimestamp`: The BREAK start timestamp, in microseconds.\n7. `packetTime`: The duration of the last packet, in microseconds, measured from\n   BREAK start to the end of the last slot.\n8. `breakTime`: The packet's BREAK time, set if RX line monitoring is enabled.\n9. `mabTime`: The packet's MAB time, set if RX line monitoring is enabled.\n\nIf the RX line is not being monitored, then the BREAK and MAB times will be set\nto zero.\n\nThere is also an optional parameter in `readPacket`, a `PacketStats*`, that\nenables retrieval of this data atomically with the packet data.\n\nThese metrics are reset to zero when the receiver is started or restarted.\n\n### Error statistics\n\nError statistics are tracked and the latest can be retrieved from an\n`ErrorStats` object returned by `errorStats()`. Included are these counts:\n\n1. `packetTimeoutCount`: Packet timeouts.\n2. `framingErrorCount`: Framing error count, including BREAKs that were\n   too short.\n3. `shortPacketCount`: Packets that were too short.\n4. `longPacketCount`: Packets that were too long.\n\n### Synchronous operation by using custom responders\n\nThere is the ability to notify specific instances of `Responder` when packets\nhaving specific start codes arrive. To implement the simplest form, simply\nextend `Responder`, override the `receivePacket` function, and attach an\ninstance to one or more start codes using `Receiver::setResponder`.\n`receivePacket` will be called for each packet received that has one of the\ndesired start codes.\n\nAs well, by default, handlers will \"eat\" packets so that they aren't available\nto callers to the `Receiver` API. To change this behaviour, override\n`Responder::eatPacket()`.\n\nFor example, let's say you want to change the local display when a text packet\narrvies. The following partial code example shows how to do this.\n\n```c++\nclass TextHandler : public teensydmx::Responder {\n public:\n  static constexpr uint8_t kStartCode = 0x17;\n\n  void receivePacket(const uint8_t *buf, int len) override {\n    // The packet must contain at least 3 bytes (plus the start code)\n    if (len \u003c 4) {\n      return;\n    }\n    uint8_t page = buf[1];\n    uint8_t charsPerLine = buf[2];\n\n    // Some checks should be made here for the data not ending in a\n    // NUL character\n\n    // Assume the existence of this function\n    setText(page, charsPerLine,\n            reinterpret_cast\u003cconst char *\u003e(\u0026buf[3]), len - 3);\n  }\n}\n\nTextHandler textHandler;\n\nvoid setup() {\n  // ...\n  dmxRx.setResponder(TextHandler::kStartCode, \u0026textHandler)\n  // ...\n\n  dmxRx.begin();\n}\n```\n\nResponders can be added at any time.\n\nComplete synchronous operation examples using SIP and text packets can be found\nin `SIPHandler` and `TextPacketHandler`.\n\n#### Responding\n\nProtocols such as RDM need the ability, not only to process specific packets,\nbut to respond to them as well. Timing is important, so a `Responder`\nimplementation can also be notified of each byte as it arrives. The function of\ninterest is `processByte`.\n\nAs bytes are received, the implementation tracks some internal state. When it is\ndecided that a response is necessary, it returns a positive value indicating how\nmany bytes it placed into the output buffer, for transmitting back to the\ntransmitter. The `Responder` needs to implement `outputBufferSize()` in order\nfor any response to be sent. `processByte` will be passed a buffer at least as\nlarge as the value returned from `outputBufferSize()`.\n\nSome other functions that specify some timings should also be implemented.\nPlease consult the `Responder.h` documentation for more details.\n\nBecause all processing happens within an interrupt context, it should execute as\nquickly as possible. Any long-running operations should be executed in the main\nloop (or some other execution context). If the protocol allows for it, the\n`Responder` can reply with a \"not yet\" response, and then return any queued\nprocessing results when ready.\n\nA more complete example is beyond the scope of this README.\n\n## DMX transmit\n\n### Code example\n\nFirst, create an object using one of the hardware serial ports, different from\nany serial ports being used for receive:\n\n```c++\nnamespace teensydmx = ::qindesign::teensydmx;\n\nteensydmx::Sender dmxTx{Serial2};\n```\n\nBefore using the instance, optionally set up the packet size and refresh rate,\nand then start the serial port internals to begin transmission:\n\n```c++\n// Optional: dmxTx.setPacketSize(100);\n// Optional: dmxTx.setRefreshRate(40);\ndmxTx.begin();\n```\n\nSet one or more channel values using one of the `set` functions:\n\n```c++\n// Set channel 6 to 219\ndmxTx.set(6, 219);\n```\n\nThe other `set` function can set multiple channels at once. This is left as an\nexercise to the reader.\n\n### Setting 16-bit values\n\nSetting 16-bit values is easy with two available 16-bit functions:\n1. `set16Bit` for single words, and\n2. `set16Bit` for an array of words.\n\nThese work the same as the 8-bit `set` functions, but use the `uint16_t`\ntype instead.\n\n### Packet size\n\nThe packet size can be adjusted and retrieved via `setPacketSize` and\n`packetSize()`. Smaller packets will naturally result in a higher\nrefresh rate.\n\nThis can be changed at any time.\n\nThe minimum packet time is 1204us, and so as long as the actual time doesn't\nfall short of this, there is no minimum packet size. The equation is:\n\n```\nPacket Size \u003e= max{(1204us - BREAK - MAB)/44us, 0}\n```\n\nNote that because the library operates asynchronously, if the packet size needs\nto be changed at the same time as the data, both must be set atomically. There\nare several ways:\n\n1. Disable the interrupts:\n   ```c++\n   __disable_irq();\n   dmxTx.setPacketSize(...);\n   dmxTx.set(...);\n   __enable_irq();\n   ```\n\n   The disadvantage of this approach is that it disables all interrupts,\n   possibly affecting the behaviour of other libraries. Instead of global\n   interrupt disable, only the IRQ of the specific serial port in use should\n   be disabled, but this is (currently) beyond the scope of this document.\n\n2. Pause and resume:\n   ```c++\n   dmxTx.pause();\n   while (dmxTx.isTransmitting()) {\n      yield();\n   }\n   dmxTx.setPacketSize(...);\n   dmxTx.set(...);\n   dmxTx.resume();\n   ```\n\n   The disadvantage of this approach is that it uses more code and effectively\n   pauses execution.\n\n3. Use the `setPacketSizeAndData()` function:\n   ```c++\n   dmxTx.setPacketSizeAndData(...);\n   ```\n\n   This is probably the easiest approach.\n\n### Transmission rate\n\nThe transmission rate can be changed from a maximum of about 44Hz down to as low\nas you wish. See the `setRefreshRate` and `refreshRate()` in `Sender`.\n\nNote that the rate won't be higher than the limits dictated by the protocol,\nabout 44Hz, no matter how high it's set. The default is, in fact, `INFINITY`.\n\nThis can be changed at any time.\n\nIf the MBB time is also specified and it would conflict with the desired rate,\nthen the larger of the two possible MBB times will be used to achieve either the\nspecified rate or the specified MBB. See [MBB time](#mbb-time) for\nmore information.\n\n### Synchronous operation by pausing and resuming\n\n`Sender` is an asynchronous packet transmitter; packets are always being sent.\nTo ensure that certain packets are adjacent to others, such as for System\nInformation Packets (SIP), the API provides a way to send packets synchronously.\n\nFirstly, the `pause()` function pauses packet transmission, the `resume()`\nfunction resumes transmission, and `resumeFor(int)` resumes transmission for a\nspecific number of packets, after which transmission is paused again.\n\nThere are two ways to achieve synchronous operation. The first is with\n`isTransmitting()`. It indicates whether the transmitter is sending anything\nwhile paused---it always returns `true` when not paused---and can be used\nto determine when it's safe to start filling in packet data after a `resumeFor`\nor `pause()` call.\n\nThe second way is to provide a function to `onDoneTransmitting`. The function\nwill be called when the same conditions checked by `isTransmitting()` occur. It\nwill be called from inside an ISR, so take this into account.\n\nIt is important to note that when utilizing the pause feature, changing data via\nthe `set` functions should only be done while not transmitting. Pausing doesn't\nimmediately stop transmission; the pause happens after the current packet is\ncompletely sent. Changing the data may affect this packet.\n\nLet's say you want to send a SIP packet immediately after a regular packet. The\nfollowing code shows how to accomplish this using the polling approach:\n\n```c++\n// Before the code starts looping, pause the transmitter\ndmxTx.pause();\n\n// Loop starts here\nfillRegularData();\ndmxTx.resumeFor(1);  // Send one regular packet\nwhile (dmxTx.isTransmitting()) {  // Wait for this packet to be sent\n  yield();\n}\nfillSIPData();\ndmxTx.resumeFor(1);  // Send the SIP data\nwhile (dmxTx.isTransmitting()) {  // Wait for this packet to be sent\n  yield();\n}\n```\n\nUsing the asynchronous notification approach requires keeping track of some\nstate, and is slightly more complex than the polling approach.\n\nOther functions of interest are `isPaused()` and `resumedRemaining()`.\n`isPaused()` indicates whether the transmitter is paused (but still potentially\ntransmitting). `resumedRemaining()` returns the number of packets that will be\nsent before the transmitter is paused again.\n\nComplete synchronous operation examples using SIP can be found in\n`SIPSenderAsync` and `SIPSenderSync`. The first uses the asynchronous\nnotification approach and the second uses the polling approach.\n\n### Choosing BREAK and MAB times\n\nThe BREAK and MAB times can be specified in two ways:\n1. By specifying specific times using the `setBreakTime` and `setMABTime`\n   functions, or\n2. By specifying serial port parameters and relying on multiples of bit times,\n   using the `setBreakSerialParams` function.\n\nThe default times are set, in both cases, to approximately 180us for the BREAK\nand approximately 20us for the MAB.\n\nThe mode is switched between these two options using the\n`setBreakUseTimerNotSerial` function. The default is to use\nserial parameters.\n\n#### Specific BREAK/MAB times\n\nThis is the first way to generate these times.\n\nThe BREAK will be transmitted with a duration reasonably close to the specified\nvalue, but the actual MAB time may be larger than requested. This has to do with\nhow the UARTs on the chip work.\n\nThis feature uses one of the _PIT_ timers via `PeriodicTimer` (or, optionally,\nthe `IntervalTimer` API), but if none are available, then the transmitter will\nfall back on using the baud rate generator with the specified serial\nport parameters.\n\n##### A note on BREAK timing\n\nThe BREAK timing is pretty accurate, but slightly shorter and longer times have\nbeen observed.\n\n(If using the `IntervalTimer` API, there's some inaccuracy. It doesn't provide a\nway to execute an action (in this case, starting a BREAK) just before the timer\nstarts. Instead, the timer will have already started and some time elapsed\nbefore the BREAK can start.\n\nEfforts have been made to make the BREAK time be at least the amount requested,\nbut it likely won't be exactly the requested duration.)\n\n##### A note on MAB timing\n\nThe MAB time may be longer than requested. The reason is that it's not possible\nto immediately start sending a character using the UART on the Teensy chips. It\nseems that the best resolution one can get is \"somewhere within one or two\nbit times\".\n\nTo illustrate:\nBREAK -\u003e(immediate) MAB -\u003e(not immediate) First packet character\n\nFor example, if a timer aims for a 23us MAB, and then a character is sent to the\nUART after the timer expires, then that character might start after 25us. If the\ntimer is adjusted so that it expires 2us earlier, then the character might still\nappear too late, still after 25us, for example. A further adjustment of 2us, for\na timer duration of 19us, might result in the character appearing after 21us,\nearlier than requested.\n\nWhen a character starts, it appears that the UART performs its functions\nasynchronously, and so it is not possible to achieve exact timing. The code\nadjusts the MAB time under the covers to achieve times that are as close to the\nrequested time as possible without going under, but it is often not possible to\nbe more precise than \"within one or two bit times\", depending on the processor.\n\n#### BREAK/MAB times using serial parameters\n\nThis is the second way to generate these times.\n\nThe times are restricted to having a BREAK:MAB ratio of 9:2, 10:2, 9:1, 10:1,\n9:3, 8:2, or 11:1. These correspond to the UART formats, 8N2, 8E2, 8N1, 8E1,\n8O2, 7O1, and 9E1. For additional information on this subject, see the\n[BREAK Timing in DMX512-A](extras/break-timing.md) note.\n\nThe BREAK time will be fairly accurate, but the MAB time will be a little longer\nthan expected, by up to at least several bit times.\n\nThis is a less flexible way to specify the BREAK and MAB times. The baud rate is\nchanged just for these and then changed back to 250kbaud (8N2) for the packet\ndata. The act of changing the baud rate can introduce a delay, somewhere up to\none full character.\n\nThis mode is also used as a fallback if the system doesn't have the\ntimers available.\n\n### Inter-slot MARK time\n\nThe inter-slot MARK time can be set with the `setInterSlotTime` function and\nretrieved using the `interSlotTime()` function. Note that the MARK time should\nbe accurate to within one or two bit times due to internal UART details. See\n[A note on MAB timing](#a-note-on-mab-timing) for more information.\n\n### MBB time\n\nThe MARK before BREAK (MBB) time can be set with the `setMBBTime` function and\nretrieved using the `mbbTime()` function. Note that the time should be accurate\nto within one or two bit times due to internal UART details. See\n[A note on MAB timing](#a-note-on-mab-timing) for more information.\n\nNote also that there will always be some minimum transmitted MBB due to how the\ncode and UART interact.\n\nIf the refresh rate is set as well then the actual MBB time will be whichever\nmakes the packet larger. For example, if the MBB is set to 100us, but adding\nthis to the packet would result in a rate that's slower than specified, then\n100us is used. On the other hand, if adding the same MBB would make the\nspecified rate faster, then enough additional time will be added so that the\nrate is correct.\n\n### Error handling in the API\n\nSeveral `Sender` functions that return a `bool` indicate whether an operation\nwas successful. Prior versions of the library did nothing with, or silently\nignored, bad arguments.\n\nThe status-returning functions are as follows:\n1. `setPacketSize`,\n2. Both `set` functions,\n3. Both `set16Bit` functions,\n4. `setRefreshRate`, and\n5. Both `resumeFor` functions.\n\n## Technical notes\n\n### Simultaneous transmit and receive\n\nThe same serial port can't be used for simultaneous transmit and receive. This\nis because the library uses the serial port hardware for data instead of direct\npin control. The break portion of a DMX frame needs to be transmitted at a\ndifferent baud rate than the slots (channels), and since reception and\ntransmission aren't necessarily synchronized, two different serial ports must\nbe used.\n\nUse `qindesign::teensydmx::Receiver` to receive and\n`qindesign::teensydmx::Sender` to transmit.\n\n### Transmission rate\n\nFrom a UART perspective, there are two parts to a DMX frame:\n\n1. BREAK, 50000 baud, 8N1.\n2. Up to 513 slots, 250000 baud, 8N2.\n\nThe total frame time is approximately:\n\n10 bits * 20 us + 513 slots * 11 bits * 4us = 22772us, or a rate of about\n43.91Hz.\n\nThe total frame time may be a little longer due to switching baud rates\ninternally and the existence of some interrupt and code execution latency.\n\n### Transmit/receive enable pins\n\nSome setups may require that an external part be enabled when transmitting or\nreceiving. For example, an RS485 transceiver may require enabling or disabling\nspecific buffers. That may be accomplished by using one of the GPIO pins. Please\nbe sure the logic levels are compatible.\n\n### Thread safety\n\nThis code is not thread-safe and should be handled appropriately if utilized in\na concurrent context.\n\n### Dynamic memory allocation failures\n\nThe `Receiver::setResponder` function dynamically allocates memory. On small\nsystems, this may fail. The caller can check for this condition by examining\n`errno` for `ENOMEM`. If this occurs, then the function will return `nullptr`,\nbut otherwise fails silently. Additionally, all responders are wiped out,\nincluding any previously-set responders.\n\n### Hardware connection\n\nDMX uses RS-485 differential signalling. This means that a transceiver is needed\nbetween the Teensy and the DMX lines. See\n[DMX512](https://en.wikipedia.org/wiki/DMX512) for DMX connector pin guidance.\n\n1. Basically, DMX uses A, B, and ground, and the Teensy serial ports use RX, TX,\n   and ground. After choosing a serial port on the Teensy, connect TX to the\n   _Driver Input_ and RX to the _Receiver Output_ of the transceiver.\n2. Since RS-485 is half-duplex, the transceiver provides a way, via either one\n   or two pins, to select between receiving and transmitting modes. For the\n   two-pin case, these likely can be merged because they're logically opposite,\n   for example, HIGH to enable the driver and LOW to enable the receiver. In\n   both cases, choose one of the Teensy's digital output pins to act as the\n   transmit-or-receive selector.\n3. Last, connect the ground of the Teensy to the ground of the transceiver.\n\nIt is beyond the scope of this document to describe how to accommodate\ntransmission line effects of long lines.\n\n### `Receiver` and driving the TX pin\n\nBy default, when a `Receiver` is used, the TX pin for its serial port is\nenabled. This means that the line is driven. This default state was chosen\nbecause custom responders may wish to transmit information. Additionally, the\ncaller does not need to remember to enable the transmitter when adding a\ncustom responder.\n\nIf it is known that there are no responders or that no responders will send\ndata, then the TX pin can be disabled so that the UART hardware isn't driving\nthe line. The `Receiver::setTXEnabled` function can be called either during\noperation or outside a `begin` and `end` pair.\n\nBe aware that if the receiver is currently in operation, enabling the\ntransmitter will cause an 11-bit idle character to be queued for output and the\nline to remain high after that.\n\n### Potential PIT timer conflicts\n\nBy default, this library internally uses PIT timers via Teensy's default\n`IntervalTimer` API. For more accurate BREAK timing, a custom API,\n`PeriodicTimer`, can be used instead. Globally define the\n`TEENSYDMX_USE_PERIODICTIMER` macro when building and the library will use this\ncustom API. However, be aware that conflicts may occur if other libraries in\nyour project use `IntervalTimer`.\n\n## Code style\n\nCode style for this project mostly follows the\n[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).\n\nOther conventions are adopted from Bjarne Stroustrup's and Herb Sutter's\n[C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md).\n\n## References\n\n1. [BREAK Timing in DMX512-A](extras/break-timing.md)\n\nInspirations for this library:\n\n1. Chris Staite's [TeensyDmx](https://github.com/chrisstaite/TeensyDmx)\n   library, which is further based on\n   Matthias Hertel's [DMXSerial2](https://github.com/mathertel/DmxSerial2),\n   Ward's\n   [DmxReceive](http://forum.pjrc.com/threads/19662-Arduinoesque-overriding-of-core-functionality?p=24993\u0026viewfull=1#post24993),\n   and\n   Paul Stoffregen's [DmxSimple](https://github.com/PaulStoffregen/DmxSimple).\n2. Claude Heintz's\n   [LXTeensy3DMX_Library](https://github.com/claudeheintz/LXTeensy3DMX_Library).\n\n## Acknowledgements\n\n1. It's one thing to have chip documentation, but it's an entirely new beast\n   when there exists code that already implements all the hard parts. Thank you\n   to Paul Stoffregen for all the Teensy source code. This helped immensely when\n   developing this library.\n   See: https://github.com/PaulStoffregen/cores\n\n---\n\nCopyright (c) 2017-2021 Shawn Silverman\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssilverman%2Fteensydmx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssilverman%2Fteensydmx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssilverman%2Fteensydmx/lists"}