├── .gitignore ├── LICENSE ├── README.md ├── examples ├── DMX2Ethernet │ ├── DMX2Ethernet.ino │ ├── LXArduinoDMXUSART.cpp │ └── LXArduinoDMXUSART.h ├── DMXEthernet_NeoPixels │ └── DMXEthernet_NeoPixels.ino ├── DMXOutputSAMD21 │ ├── DMXOutputSAMD21.ino │ ├── LXSAMD21DMX.cpp │ └── LXSAMD21DMX.h ├── DMXOutputUsingUSART │ ├── DMXOutputUsingUSART.ino │ ├── LXArduinoDMXUSART.cpp │ └── LXArduinoDMXUSART.h ├── OutputToNetwork │ └── OutputToNetwork.ino └── TwoUniverses │ └── TwoUniverses.ino ├── extras └── doc │ ├── LXArtNet_h │ ├── Classes │ │ └── LXArtNet │ │ │ ├── index.html │ │ │ └── toc.html │ ├── index.html │ └── toc.html │ ├── LXDMXEthernet_h │ ├── Classes │ │ └── LXDMXEthernet │ │ │ ├── index.html │ │ │ └── toc.html │ ├── index.html │ └── toc.html │ ├── LXSACN_h │ ├── Classes │ │ └── LXSACN │ │ │ ├── index.html │ │ │ └── toc.html │ ├── index.html │ └── toc.html │ └── index.html ├── keywords.txt ├── library.properties └── src ├── LXArtNet.cpp ├── LXArtNet.h ├── LXDMXEthernet.h ├── LXSACN.cpp └── LXSACN.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Claude Heintz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of LXDMXEthernet_library nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LXDMXEthernet_library 2 | Library for Arduino with Ethernet Shield implements Art-Net and sACN with example DMX output using USART 3 | 4 | LXDMXEthernet encapsulates functionality for sending and receiving DMX over ethernet. 5 | It is a virtual class with concrete subclasses LXArtNet and LXSACN which specifically 6 | implement the Artistic Licence Art-Net and PLASA sACN 1.31 protocols. 7 | 8 | Note: LXDMXEthernet_library requires 9 | Ethernet library used with the original Arduino Ethernet Shield 10 | For the Ethernet v2 shield, use the branch that has includes for Ethernet2.h and EthernetUdp2.h 11 | 12 | For multicast, EthernetUDP.h and EthernetUDP.cpp in the Ethernet library 13 | must be modified to add the beginMulticast method. 14 | See the code at the bottom of LXDMXEthernet.h 15 | 16 | Included examples of the library's use: 17 | 18 | Art-Net or sACN controlling Adafruit NeoPixels 19 | 20 | DMX output using the AVR's USART and an external line driver chip 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | LXSeries open source projects are free to download and have BSD style licenses so their code can be reused by anyone. 29 | If you would like to support LXSeries open source projects and free applications, you can make a donation: 30 | https://www.claudeheintzdesign.com/lx/lxseries_opensource_donate.html 31 | -------------------------------------------------------------------------------- /examples/DMX2Ethernet/DMX2Ethernet.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file DMX2Ethernet.ino 4 | @author Claude Heintz 5 | @license BSD (see LXDMXEthernet.h) 6 | @copyright 2015-2017 by Claude Heintz 7 | 8 | Example using LXDMXEthernet_Library for input from DMX to Art-Net or E1.31 sACN 9 | 10 | Art-Net(TM) Designed by and Copyright Artistic Licence Holdings Ltd. 11 | sACN E 1.31 is a public standard published by the PLASA technical standards program 12 | 13 | See LXDMXEthernet.h or http://lx.claudeheintzdesign.com/opensource.html for license. 14 | 15 | @section HISTORY 16 | 17 | v1.00 - First release 18 | 19 | */ 20 | /*********************** includes ***********************/ 21 | 22 | #include "LXArduinoDMXUSART.h" 23 | 24 | #include 25 | 26 | /******** Important note about Ethernet library ******** 27 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 28 | It is necessary to use an Ethernet library that supports the correct chip for your shield 29 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 30 | 31 | https://github.com/PaulStoffregen/Ethernet 32 | 33 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 34 | neccessary if the shield receives more than a single universe of packets. 35 | 36 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 37 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 38 | uncomment the line to define ETHERNET_SHIELD_V2 39 | */ 40 | 41 | #define ETHERNET_SHIELD_V2 1 42 | 43 | #if defined ( ETHERNET_SHIELD_V2 ) 44 | #include 45 | #include 46 | #else 47 | #include 48 | #include 49 | #endif 50 | 51 | #include 52 | #include 53 | #include 54 | 55 | //*********************** defines *********************** 56 | 57 | /* 58 | Enter a MAC address and IP address for your controller below. 59 | The MAC address can be random as is the one shown, but should 60 | not match any other MAC address on your network. 61 | 62 | If BROADCAST_IP is not defined, ArtPollReply will be sent directly to server 63 | rather than being broadcast. (If a server' socket is bound to a specific network 64 | interface ip address, it will not receive broadcast packets.) 65 | */ 66 | 67 | #define USE_DHCP 1 68 | #define USE_SACN 0 69 | // comment out next line for unicast sACN 70 | //#define USE_MULTICAST 1 71 | 72 | #define MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0F, 0xF7, 0x6D 73 | #define IP_ADDRESS 192,168,1,20 74 | #define GATEWAY_IP 192,168,1,1 75 | #define SUBNET_MASK 255,255,255,0 76 | #define BROADCAST_IP 192,168,1,255 77 | // this is the IP address to which output packets are sent, unless multicast is used 78 | #define TARGET_IP 192,168,1,255 79 | 80 | // this sketch flashes an indicator led: 81 | #define LED_PIN 3 82 | 83 | // the driver direction is controlled by: 84 | #define RXTX_PIN 2 85 | 86 | //the Ethernet Shield has an SD card that also communicates by SPI 87 | //set its select pin to output to be safe: 88 | #define SDSELECT_PIN 4 89 | 90 | //*********************** globals *********************** 91 | 92 | //network addresses 93 | byte mac[] = { MAC_ADDRESS }; 94 | IPAddress ip(IP_ADDRESS); 95 | IPAddress gateway(GATEWAY_IP); 96 | IPAddress subnet_mask(SUBNET_MASK); 97 | 98 | #if defined( BROADCAST_IP ) 99 | IPAddress broadcast_ip( BROADCAST_IP); 100 | #else 101 | IPAddress broadcast_ip = INADDR_NONE; 102 | #endif 103 | 104 | // buffer 105 | unsigned char packetBuffer[ARTNET_BUFFER_MAX]; 106 | 107 | // An EthernetUDP instance to let us send and receive packets over UDP 108 | EthernetUDP eUDP; 109 | 110 | // LXDMXEthernet instance ( created in setup so its possible to get IP if DHCP is used ) 111 | LXDMXEthernet* interface; 112 | 113 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 114 | #if defined ( USE_MULTICAST ) 115 | uint8_t use_multicast = USE_SACN; 116 | #else 117 | uint8_t use_multicast = 0; 118 | #endif 119 | 120 | 121 | uint8_t led_state = 0; 122 | uint8_t loop_counter = 0; 123 | 124 | void blinkLED() { 125 | if ( led_state ) { 126 | digitalWrite(LED_PIN, HIGH); 127 | led_state = 0; 128 | } else { 129 | digitalWrite(LED_PIN, LOW); 130 | led_state = 1; 131 | } 132 | } 133 | 134 | int got_dmx = 0; 135 | void gotDMXCallback(int slots); 136 | IPAddress send_address; 137 | 138 | void setup() { 139 | pinMode(LED_PIN, OUTPUT); //status LED 140 | blinkLED(); 141 | #if defined(SDSELECT_PIN) 142 | pinMode(SDSELECT_PIN, OUTPUT); 143 | #endif 144 | 145 | if ( USE_DHCP ) { // Initialize Ethernet 146 | Ethernet.begin(mac); // DHCP 147 | } else { 148 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 149 | } 150 | 151 | blinkLED(); 152 | 153 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 154 | interface = new LXSACN(); 155 | //interface->setUniverse(1); // for different universe, change this line and the multicast address below 156 | if ( use_multicast ) { 157 | send_address = IPAddress(239,255,0,1); 158 | } else { 159 | send_address = IPAddress(TARGET_IP); 160 | } 161 | } else { 162 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask()); 163 | #if defined( BROADCAST_IP ) 164 | send_address = broadcast_ip; 165 | #else 166 | send_address = IPAddress(TARGET_IP); 167 | #endif 168 | 169 | ((LXArtNet*) interface)->setOutputFromNetworkMode(0); //disables receiving ArtDMX 170 | ((LXArtNet*) interface)->setArtPollReplyCallback(&gotPollReplyCallback); 171 | ((LXArtNet*)interface)->setSubnetUniverse(0, 0); //for different subnet/universe, change this line 172 | } 173 | 174 | LXSerialDMX.setDirectionPin(RXTX_PIN); 175 | LXSerialDMX.setDataReceivedCallback(&gotDMXCallback); 176 | LXSerialDMX.startInput(); 177 | 178 | if ( use_multicast ) { 179 | eUDP.beginMulticast(send_address, interface->dmxPort()); 180 | } else { 181 | eUDP.begin(interface->dmxPort()); 182 | } 183 | 184 | if ( USE_SACN == 0 ) { 185 | ((LXArtNet*) interface)->send_art_poll(&eUDP); 186 | } 187 | 188 | blinkLED(); 189 | } 190 | 191 | 192 | // ***************** input callback function ************* 193 | 194 | void gotDMXCallback(int slots) { 195 | got_dmx = slots; 196 | } 197 | 198 | 199 | // ***************** Art-poll reply callback function ************* 200 | 201 | void gotPollReplyCallback(uint8_t* packet) { 202 | if ( packet[174] & 0x80 ) { //1st port can output from network 203 | send_address = IPAddress(packet[10], packet[11], packet[12], packet[13]); 204 | } 205 | } 206 | 207 | /************************************************************************ 208 | 209 | The main loop fades the levels of addresses 7 and 8 to full 210 | 211 | *************************************************************************/ 212 | 213 | void loop() { 214 | if ( got_dmx ) { 215 | interface->setNumberOfSlots(got_dmx); 216 | for(int i=1; i<=got_dmx; i++) { 217 | interface->setSlot(i, LXSerialDMX.getSlot(i)); 218 | } 219 | blinkLED(); 220 | 221 | //interface->setNumberOfSlots(512); 222 | interface->sendDMX(&eUDP, send_address); 223 | 224 | if ( USE_SACN == 0 ) { 225 | loop_counter++; 226 | if ( loop_counter > 132 ) { 227 | loop_counter = 0; 228 | ((LXArtNet*) interface)->send_art_poll(&eUDP); 229 | } 230 | interface->readDMXPacket(&eUDP); // allows responding to Art-Net polls, but not ArtDMX 231 | // will set interface slots to zero when an Art-Net packet is received 232 | } 233 | 234 | 235 | 236 | got_dmx = 0; //wait for next serial DMX break 237 | } else { 238 | digitalWrite(LED_PIN, LOW); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /examples/DMX2Ethernet/LXArduinoDMXUSART.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file LXUSARTDMX.cpp 4 | @author Claude Heintz 5 | @license BSD (see LXUSARTDMX.h) 6 | @copyright 2015-2016 by Claude Heintz 7 | 8 | DMX Driver for Arduino using USART. 9 | 10 | @section HISTORY 11 | 12 | v1.0 First release 13 | v1.1 - Consolidated input and output into single class 14 | */ 15 | /**************************************************************************/ 16 | #include "pins_arduino.h" 17 | #include "LXArduinoDMXUSART.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | LXUSARTDMX LXSerialDMX; 24 | 25 | // ***************** define registers, flags and interrupts **************** 26 | 27 | // processors with single UART such AT328 processor (Uno) 28 | // should not define USE_UART_1 29 | // Uncomment the following to use UART1: 30 | //#define USE_UART_1 31 | 32 | // USE_UART_1 should be defined for Teensy 2.0++, Leonardo, Yun 33 | #if defined(__AVR_AT90USB1286__) || defined (__AVR_ATmega32U4__) 34 | #define USE_UART_1 35 | #endif 36 | 37 | 38 | #if !defined(USE_UART_1) // definitions for UART0 39 | // for AT328 see datasheet pg 194 40 | 41 | #define LXUCSRA UCSR0A // USART register A 42 | #define LXTXC TXC0 // tx buffer empty 43 | #define LXUDRE UDRE0 // data ready 44 | #define LXFE FE0 // frame error 45 | #define LXU2X U2X0 // double speed 46 | 47 | #define LXUCSRB UCSR0B // USART register B 48 | #define LXRXCIE RXCIE0 // rx interrupt enable bit 49 | #define LXTXCIE TXCIE0 // tx interrupt enable bit 50 | #define LXRXEN RXEN0 // rx enable bit 51 | #define LXTXEN TXEN0 // tx enable bit 52 | 53 | #define LXUCSRC UCSR0C // USART register C 54 | #define LXUSBS0 USBS0 // stop bits 55 | #define LXUCSZ0 UCSZ00 // length 56 | #define LXUPM0 UPM00 // parity 57 | #define LXUCSRRH UBRR0H // USART baud rate register msb 58 | #define LXUCSRRL UBRR0L // USARTbaud rate register msb 59 | #define LXUDR UDR0 // USART data register tx/rx 60 | 61 | #define LXUSART_RX_vect USART_RX_vect // RX ISR 62 | #define LXUSART_TX_vect USART_TX_vect // TX ISR 63 | 64 | #define BIT_FRAME_ERROR (1<>8); 173 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 174 | LXUCSRA &= ~BIT_2X_SPEED; 175 | 176 | LXUDR = 0x0; //USART send register 177 | _shared_dmx_data = dmxData(); 178 | _shared_dmx_state = DMX_STATE_BREAK; 179 | 180 | LXUCSRC = FORMAT_8N2; //set length && stopbits (no parity) 181 | LXUCSRB |= BIT_TX_ENABLE | BIT_TX_ISR_ENABLE; //enable tx and tx interrupt 182 | _interrupt_status = ISR_OUTPUT_ENABLED; 183 | } 184 | } 185 | 186 | // ***** start ***** 187 | // sets up baud rate, bits and parity 188 | // sets globals accessed in ISR 189 | // enables transmission and tx interrupt 190 | 191 | void LXUSARTDMX::startInput ( void ) { 192 | if ( _direction_pin != DIRECTION_PIN_NOT_USED ) { 193 | digitalWrite(_direction_pin, LOW); 194 | } 195 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 196 | stop(); 197 | } 198 | if ( _interrupt_status == ISR_DISABLED ) { //prevent messing up sequence if already started... 199 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1)>>8); 200 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 201 | LXUCSRA &= ~BIT_2X_SPEED; 202 | 203 | _shared_dmx_data = dmxData(); 204 | _shared_dmx_state = DMX_STATE_IDLE; 205 | _shared_dmx_slot = 0; 206 | 207 | LXUCSRC = FORMAT_8N2; //set length && stopbits (no parity) 208 | LXUCSRB |= BIT_RX_ENABLE | BIT_RX_ISR_ENABLE; //enable tx and tx interrupt 209 | _interrupt_status = ISR_INPUT_ENABLED; 210 | } 211 | } 212 | 213 | // ***** stop ***** 214 | // disables interrupts 215 | 216 | void LXUSARTDMX::stop ( void ) { 217 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 218 | LXUCSRB &= ~BIT_TX_ISR_ENABLE; //disable tx interrupt 219 | LXUCSRB &= ~BIT_TX_ENABLE; //disable tx enable 220 | } else if ( _interrupt_status == ISR_INPUT_ENABLED ) { 221 | LXUCSRB &= ~BIT_RX_ISR_ENABLE; //disable rx interrupt 222 | LXUCSRB &= ~BIT_RX_ENABLE; //disable rx enable 223 | } 224 | _interrupt_status = ISR_DISABLED; 225 | } 226 | 227 | void LXUSARTDMX::setDirectionPin( uint8_t pin ) { 228 | _direction_pin = pin; 229 | pinMode(_direction_pin, OUTPUT); 230 | } 231 | 232 | // ***** setMaxSlots ***** 233 | // sets the number of slots sent per DMX frame 234 | // defaults to 512 or DMX_MAX_SLOTS 235 | // should be no less DMX_MIN_SLOTS slots 236 | // the DMX standard specifies min break to break time no less than 1024 usecs 237 | // at 44 usecs per slot ~= 24 238 | 239 | void LXUSARTDMX::setMaxSlots (int slots) { 240 | _shared_max_slots = max(slots, DMX_MIN_SLOTS); 241 | } 242 | 243 | // ***** getSlot ***** 244 | // reads the value of a slot 245 | // see buffering note for ISR below 246 | 247 | uint8_t LXUSARTDMX::getSlot (int slot) { 248 | return _dmxData[slot]; 249 | } 250 | 251 | // ***** setSlot ***** 252 | // sets the output value of a slot 253 | 254 | void LXUSARTDMX::setSlot (int slot, uint8_t value) { 255 | _dmxData[slot] = value; 256 | } 257 | 258 | // ***** dmxData ***** 259 | // pointer to data buffer 260 | 261 | uint8_t* LXUSARTDMX::dmxData(void) { 262 | return &_dmxData[0]; 263 | } 264 | 265 | // ***** setDataReceivedCallback ***** 266 | // sets pointer to function that is called 267 | // on the break after a frame has been received 268 | // whatever happens in this function should be quick 269 | // ie set a flag so that processing of the received data happens 270 | // outside of the ISR. 271 | 272 | 273 | void LXUSARTDMX::setDataReceivedCallback(LXRecvCallback callback) { 274 | _shared_receive_callback = callback; 275 | } 276 | 277 | 278 | //************************************************************************************ 279 | // ************************ TX ISR (transmit interrupt service routine) ************* 280 | // 281 | // this routine is called when USART transmission is complete 282 | // what this does is to send the next byte 283 | // when that byte is done being sent, the ISR is called again 284 | // and the cycle repeats... 285 | // until _shared_max_slots worth of bytes have been sent on succesive triggers of the ISR 286 | // and then on the next ISR... 287 | // the break/mark after break is sent at a different speed 288 | // and then on the next ISR... 289 | // the start code is sent 290 | // and then on the next ISR... 291 | // the next data byte is sent 292 | // and the cycle repeats... 293 | 294 | 295 | ISR (LXUSART_TX_vect) { 296 | switch ( _shared_dmx_state ) { 297 | 298 | case DMX_STATE_BREAK: 299 | // set the slower baud rate and send the break 300 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_BREAK_BAUD * 8L) / (DMX_BREAK_BAUD * 16L) - 1)>>8); 301 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_BREAK_BAUD * 8L) / (DMX_BREAK_BAUD * 16L) - 1); 302 | LXUCSRA &= ~BIT_2X_SPEED; 303 | LXUCSRC = FORMAT_8E1; 304 | _shared_dmx_state = DMX_STATE_START; 305 | LXUDR = 0x0; 306 | break; // <- DMX_STATE_BREAK 307 | 308 | case DMX_STATE_START: 309 | // set the baud to full speed and send the start code 310 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1)>>8); 311 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 312 | LXUCSRA &= ~BIT_2X_SPEED; 313 | LXUCSRC = FORMAT_8N2; 314 | _shared_dmx_slot = 0; 315 | LXUDR = _shared_dmx_data[_shared_dmx_slot++]; //send next slot (start code) 316 | _shared_dmx_state = DMX_STATE_DATA; 317 | break; // <- DMX_STATE_START 318 | 319 | case DMX_STATE_DATA: 320 | // send the next data byte until the end is reached 321 | LXUDR = _shared_dmx_data[_shared_dmx_slot++]; //send next slot 322 | if ( _shared_dmx_slot > _shared_max_slots ) { 323 | _shared_dmx_state = DMX_STATE_BREAK; 324 | } 325 | break; // <- DMX_STATE_DATA 326 | } 327 | } 328 | 329 | //*********************************************************************************** 330 | // ************************ RX ISR (receive interrupt service routine) ************* 331 | // 332 | // this routine is called when USART receives data 333 | // wait for break: if have previously read data call callback function 334 | // then on next receive: check start code 335 | // then on next receive: read data until done (in which case idle) 336 | // 337 | // NOTE: data is not double buffered 338 | // so a complete single frame is not guaranteed 339 | // the ISR will continue to read the next frame into the buffer 340 | 341 | ISR (LXUSART_RX_vect) { 342 | uint8_t status_register = LXUCSRA; 343 | uint8_t incoming_byte = LXUDR; 344 | 345 | if ( status_register & BIT_FRAME_ERROR ) { 346 | _shared_dmx_state = DMX_STATE_BREAK; 347 | if ( _shared_dmx_slot > 0 ) { 348 | if ( _shared_receive_callback != NULL ) { 349 | _shared_receive_callback(_shared_dmx_slot); 350 | } 351 | } 352 | _shared_dmx_slot = 0; 353 | return; 354 | } 355 | 356 | switch ( _shared_dmx_state ) { 357 | 358 | case DMX_STATE_BREAK: 359 | if ( incoming_byte == 0 ) { //start code == zero (DMX) 360 | _shared_dmx_state = DMX_STATE_DATA; 361 | _shared_dmx_slot = 1; 362 | } else { 363 | _shared_dmx_state = DMX_STATE_IDLE; 364 | } 365 | break; 366 | 367 | case DMX_STATE_DATA: 368 | _shared_dmx_data[_shared_dmx_slot++] = incoming_byte; 369 | if ( _shared_dmx_slot > DMX_MAX_SLOTS ) { 370 | _shared_dmx_state = DMX_STATE_IDLE; // go to idle, wait for next break 371 | } 372 | break; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /examples/DMX2Ethernet/LXArduinoDMXUSART.h: -------------------------------------------------------------------------------- 1 | /* LXArduinoDMX.h 2 | Copyright 2015-2016 by Claude Heintz Design 3 | 4 | ----------------------------------- License ----------------------------------- 5 | LXArduinoDMX is free software: you can redistribute it and/or modify 6 | it for any purpose provided the following conditions are met: 7 | 8 | 1) Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2) Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3) Neither the name of the copyright owners nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | LXArduinoDMX is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 24 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 25 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ----------------------------------------------------------------------------------- 33 | 34 | The LXArduinoDMX library supports output and input of DMX using the USART 35 | serial output of an Arduino's microcontroller. With microcontrollers with a single 36 | USART such as the AT328 used by an Arduino Uno, this means that the Serial library 37 | conflicts with LXArduinoDMX and cannot be used. Other chips have more than one 38 | USART and can support both Serial and LXArduinoDMX by specifying that LXArduinoDMX uses 39 | USART1 instead of USART0. 40 | 41 | This is the circuit for a simple unisolated DMX Shield 42 | that could be used with LXArdunoDMX. It uses a line driver IC 43 | to convert the output from the Arduino to DMX: 44 | 45 | Arduino Pin 46 | | SN 75176 A or MAX 481CPA 47 | V _______________ 48 | | | 1 Vcc 8 |------(+5v) 49 | RX (0) |----------------------| | DMX Output 50 | | +----| 2 B 7 |---------------- Pin 2 51 | | | | | 52 | (2) |----------------------| 3 DE A 6 |---------------- Pin 3 53 | | | | 54 | TX (1) |----------------------| 4 DI Gnd 5 |---+------------ Pin 1 55 | | | 56 | | (GND) 57 | 58 | Data Enable (DE) and Inverted Read Enable (!RE) can be wired to +5v for output 59 | or Gnd for input, if direction switching is not needed. 60 | 61 | 62 | */ 63 | 64 | #ifndef LXusartDMX_H 65 | #define LXusartDMX_H 66 | 67 | #include 68 | #include 69 | 70 | #define DMX_MIN_SLOTS 24 71 | #define DMX_MAX_SLOTS 512 72 | 73 | #define DIRECTION_PIN_NOT_USED 255 74 | 75 | typedef void (*LXRecvCallback)(int); 76 | 77 | /*! 78 | @class LXUSARTDMX 79 | @abstract 80 | LXUSARTDMX is a driver for sending or receiving DMX using an AVR family microcontroller 81 | UART0 RX pin 0, TX pin 1 82 | 83 | LXUSARTDMX output mode continuously sends DMX once its interrupts have been enabled using startOutput(). 84 | Use setSlot() to set the level value for a particular DMX dimmer/address/channel. 85 | 86 | LXUSARTDMX input mode continuously receives DMX once its interrupts have been enabled using startInput() 87 | Use getSlot() to read the level value for a particular DMX dimmer/address/channel. 88 | 89 | LXUSARTDMX is used with a single instance called LXSerialDMX . 90 | */ 91 | 92 | class LXUSARTDMX { 93 | 94 | public: 95 | LXUSARTDMX ( void ); 96 | ~LXUSARTDMX ( void ); 97 | 98 | /*! 99 | * @brief starts interrupt that continuously sends DMX data 100 | * @discussion sets up baud rate, bits and parity, 101 | * sets globals accessed in ISR, 102 | * enables transmission and tx interrupt 103 | */ 104 | void startOutput( void ); 105 | 106 | /*! 107 | * @brief starts interrupt that continuously reads DMX data 108 | * @discussion sets up baud rate, bits and parity, 109 | * sets globals accessed in ISR, 110 | * enables receive and rx interrupt 111 | */ 112 | void startInput( void ); 113 | 114 | /*! 115 | * @brief disables USART 116 | */ 117 | void stop( void ); 118 | 119 | /*! 120 | * @brief optional utility sets the pin used to control driver chip's 121 | * DE (data enable) line, HIGH for output, LOW for input. 122 | * @param pin to be automatically set for input/output direction 123 | */ 124 | void setDirectionPin( uint8_t pin ); 125 | 126 | /*! 127 | * @brief Sets the number of slots (aka addresses or channels) sent per DMX frame. 128 | * @discussion defaults to 512 or DMX_MAX_SLOTS and should be no less DMX_MIN_SLOTS slots. 129 | * The DMX standard specifies min break to break time no less than 1024 usecs. 130 | * At 44 usecs per slot ~= 24 131 | * @param slot the highest slot number (~24 to 512) 132 | */ 133 | void setMaxSlots (int slot); 134 | 135 | /*! 136 | * @brief reads the value of a slot/address/channel 137 | * @discussion NOTE: Data is not double buffered. 138 | * So a complete single frame is not guaranteed. 139 | * The ISR continuously reads the next frame into the buffer 140 | * @return level (0-255) 141 | */ 142 | uint8_t getSlot (int slot); 143 | 144 | /*! 145 | * @brief Sets the output value of a slot 146 | * @param slot number of the slot/address/channel (1-512) 147 | * @param value level (0-255) 148 | */ 149 | void setSlot (int slot, uint8_t value); 150 | 151 | /*! 152 | * @brief provides direct access to data array 153 | * @return pointer to dmx array 154 | */ 155 | uint8_t* dmxData(void); 156 | 157 | /*! 158 | * @brief Function called when DMX frame has been read 159 | * @discussion Sets a pointer to a function that is called 160 | * on the break after a DMX frame has been received. 161 | * Whatever happens in this function should be quick! 162 | * Best used to set a flag that is polled outside of ISR for available data. 163 | */ 164 | void setDataReceivedCallback(LXRecvCallback callback); 165 | 166 | private: 167 | /*! 168 | * @brief Indicates mode ISR_OUTPUT_ENABLED or ISR_INPUT_ENABLED or ISR_DISABLED 169 | */ 170 | uint8_t _interrupt_status; 171 | 172 | /*! 173 | * @brief pin used to control direction of output driver chip 174 | */ 175 | uint8_t _direction_pin; 176 | 177 | /*! 178 | * @brief Array of dmx data including start code 179 | */ 180 | uint8_t _dmxData[DMX_MAX_SLOTS+1]; 181 | }; 182 | 183 | extern LXUSARTDMX LXSerialDMX; 184 | 185 | #endif // ifndef LXArduinoDMX_H 186 | -------------------------------------------------------------------------------- /examples/DMXEthernet_NeoPixels/DMXEthernet_NeoPixels.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /******** Important note about Ethernet library ******** 4 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 5 | It is necessary to use an Ethernet library that supports the correct chip for your shield 6 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 7 | 8 | https://github.com/PaulStoffregen/Ethernet 9 | 10 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 11 | neccessary if the shield receives more than a single universe of packets. 12 | 13 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 14 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 15 | uncomment the line to define ETHERNET_SHIELD_V2 16 | */ 17 | #define ETHERNET_SHIELD_V2 1 18 | 19 | #if defined ( ETHERNET_SHIELD_V2 ) 20 | #include 21 | #include 22 | #else 23 | #include 24 | #include 25 | #endif 26 | 27 | #include 28 | #ifdef __AVR__ 29 | #include 30 | #endif 31 | #include 32 | #include 33 | #include 34 | 35 | //*********************** defines *********************** 36 | 37 | #define PIN 6 38 | #define NUM_LEDS 12 39 | 40 | // Make choices here about protocol ( set to 1 to activate option ) 41 | 42 | #define USE_DHCP 1 43 | #define USE_SACN 0 44 | #define ARTNET_NODE_NAME "my Art-Net node" 45 | 46 | // Uncomment to use multicast, which requires extended Ethernet library 47 | // see note in LXDMXEthernet.h file about method added to library 48 | 49 | //#define USE_MULTICAST 1 50 | 51 | //*********************** globals *********************** 52 | 53 | // network addresses 54 | // IP address is defined assuming that the Arduino is connected to 55 | // a Mac without a router and a self assigned ip 56 | byte mac[] = { 0x00, 0x08, 0xDC, 0x4C, 0x29, 0x7E }; //00:08:DC Wiznet 57 | IPAddress ip(169,254,100,100); 58 | IPAddress gateway(169,254,1,1); 59 | IPAddress subnet_mask(255,255,0,0); 60 | 61 | // An EthernetUDP instance to let us send and receive packets over UDP 62 | EthernetUDP eUDP; 63 | 64 | // Adafruit_NeoPixel instance to drive the NeoPixels 65 | Adafruit_NeoPixel ring = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800); 66 | 67 | // LXDMXEthernet instance ( created in setup so its possible to get IP if DHCP is used ) 68 | LXDMXEthernet* interface; 69 | 70 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 71 | uint8_t use_multicast = USE_SACN; 72 | 73 | 74 | //*********************** setup *********************** 75 | void setup() { 76 | 77 | if ( USE_DHCP ) { // Initialize Ethernet 78 | Ethernet.begin(mac); // DHCP 79 | } else { 80 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 81 | } 82 | 83 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 84 | interface = new LXSACN(); 85 | //interface->setUniverse(1); // for different universe, change this line and the multicast address below 86 | } else { 87 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask()); 88 | use_multicast = 0; 89 | //((LXArtNet*)interface)->setSubnetUniverse(0, 0); //for different subnet/universe, change this line 90 | } 91 | 92 | if ( use_multicast ) { // Start listening for UDP on port 93 | eUDP.beginMulticast(IPAddress(239,255,0,1), interface->dmxPort()); 94 | } else { 95 | eUDP.begin(interface->dmxPort()); 96 | } 97 | 98 | ring.begin(); // Initialize NeoPixel driver 99 | ring.show(); 100 | 101 | if ( ! USE_SACN ) { 102 | ((LXArtNet*)interface)->setNodeName(ARTNET_NODE_NAME); 103 | ((LXArtNet*)interface)->send_art_poll_reply(&eUDP); 104 | } 105 | } 106 | 107 | //*********************** main loop ******************* 108 | void loop() { 109 | uint16_t r,g,b; 110 | uint16_t i; 111 | 112 | // read a packet and if the packet is dmx, write its data to the pixels 113 | if ( interface->readDMXPacket(&eUDP) == RESULT_DMX_RECEIVED ) { 114 | for (int p=0; pgetSlot(i+1); // Red 118 | g = interface->getSlot(i+2); // Green 119 | b = interface->getSlot(i+3); // Blue 120 | // gamma correct 121 | r = (r*r)/255; 122 | g = (g*g)/255; 123 | b = (b*b)/255; 124 | // write to NeoPixel Driver 125 | ring.setPixelColor(p, r, g, b); 126 | } 127 | // send to NeoPixel Ring 128 | ring.show(); 129 | } 130 | 131 | if ( USE_DHCP ) { 132 | uint8_t dhcpr = Ethernet.maintain(); 133 | if (( dhcpr == 4 ) || (dhcpr == 2)) { //renew/rebind success 134 | if ( ! USE_SACN ) { 135 | ((LXArtNet*)interface)->setLocalIP(Ethernet.localIP(), Ethernet.subnetMask()); 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /examples/DMXOutputSAMD21/DMXOutputSAMD21.ino: -------------------------------------------------------------------------------- 1 | /* 2 | DMXOutputSAMD21 3 | 4 | This sketch demonstrates converting Art-Net or sACN packets recieved using an 5 | Arduino Ethernet Shield to DMX output using serial output to a driver chip. 6 | 7 | This sketch requires software such as LXConsole, QLC+ or DMXWorkshop 8 | that can send Art-Net or sACN packets to the Arduino. 9 | 10 | The sketch receives incoming UDP packets. If a received packet is 11 | Art-Net/sACN DMX output, it uses one of the the SAMD-21's SERCOM (serial communication) 12 | peripherals to output the dmx levels as serial data. A driver chip is used 13 | to convert this output to a differential signal (actual DMX). 14 | 15 | This is the circuit for a simple unisolated DMX Shield: 16 | 17 | Arduino SN 75176 A or MAX 481CPA 18 | pin _______________ 19 | | | 1 Vcc 8 |------ +5v 20 | V | | DMX Output 21 | | +----| 2 B 7 |---------------- Pin 2 22 | | | | | 23 | 3 |----------------------| 3 DE A 6 |---------------- Pin 3 24 | | | | 25 | 4 |----------------------| 4 DI Gnd 5 |---+------------ Pin 1 26 | | | 27 | | GND 28 | 29 | Created May 4th, 2014 by Claude Heintz 30 | Current version 1.0 (see bottom of file for revision history) 31 | 32 | See LXSAMD21DMX.h or http://lx.claudeheintzdesign.com/opensource.html for license. 33 | Art-Net(tm) Designed by and Copyright Artistic Licence Holdings Ltd. 34 | 35 | */ 36 | 37 | //*********************** includes *********************** 38 | 39 | #include "LXSAMD21DMX.h" 40 | 41 | #include 42 | 43 | /******** Important note about Ethernet library ******** 44 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 45 | It is necessary to use an Ethernet library that supports the correct chip for your shield 46 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 47 | 48 | https://github.com/PaulStoffregen/Ethernet 49 | 50 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 51 | neccessary if the shield receives more than a single universe of packets. 52 | 53 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 54 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 55 | uncomment the line to define ETHERNET_SHIELD_V2 56 | */ 57 | #if defined ( ETHERNET_SHIELD_V2 ) 58 | #include 59 | #include 60 | #else 61 | #include 62 | #include 63 | #endif 64 | 65 | #include 66 | #include 67 | #include 68 | 69 | //*********************** defines *********************** 70 | 71 | /* 72 | Enter a MAC address and IP address for your controller below. 73 | The MAC address can be random as is the one shown, but should 74 | not match any other MAC address on your network. 75 | 76 | If BROADCAST_IP is not defined, ArtPollReply will be sent directly to server 77 | rather than being broadcast. (If a server' socket is bound to a specific network 78 | interface ip address, it will not receive broadcast packets.) 79 | */ 80 | 81 | #define USE_DHCP 0 82 | #define USE_SACN 0 83 | 84 | // Uncomment to use multicast, which requires extended Ethernet library 85 | // see note in LXDMXEthernet.h file about method added to library 86 | 87 | //#define USE_MULTICAST 1 88 | 89 | #define MAC_ADDRESS 0xDD, 0x43, 0x34, 0x4C, 0x29, 0x7E 90 | #define IP_ADDRESS 192,168,1,140 91 | #define GATEWAY_IP 192,168,1,1 92 | #define SUBNET_MASK 255,255,255,0 93 | #define BROADCAST_IP 192,168,1,255 94 | 95 | // this sketch flashes an indicator led: 96 | #define MONITOR_PIN 5 97 | 98 | // the driver direction is controlled by: 99 | #define RXTX_PIN 2 100 | 101 | //the Ethernet Shield has an SD card that also communicates by SPI 102 | //set its select pin to output to be safe: 103 | #define SDSELECT_PIN 4 104 | 105 | 106 | //*********************** globals *********************** 107 | 108 | //network addresses 109 | byte mac[] = { MAC_ADDRESS }; 110 | IPAddress ip(IP_ADDRESS); 111 | IPAddress gateway(GATEWAY_IP); 112 | IPAddress subnet_mask(SUBNET_MASK); 113 | 114 | #if defined( BROADCAST_IP ) 115 | IPAddress broadcast_ip( BROADCAST_IP); 116 | #else 117 | IPAddress broadcast_ip = INADDR_NONE; 118 | #endif 119 | 120 | // buffer 121 | unsigned char packetBuffer[ARTNET_BUFFER_MAX]; 122 | 123 | // An EthernetUDP instance to let us send and receive packets over UDP 124 | EthernetUDP eUDP; 125 | 126 | // LXDMXEthernet instance ( created in setup so its possible to get IP if DHCP is used ) 127 | LXDMXEthernet* interface; 128 | 129 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 130 | uint8_t use_multicast = USE_SACN; 131 | 132 | // used to toggle on and off the LED when DMX is Received 133 | int monitorstate = LOW; 134 | 135 | 136 | /* setup initializes Ethernet and opens the UDP port 137 | it also sends an Art-Net Poll Reply packet telling other 138 | Art-Net devices that it can transmit DMX from the network */ 139 | 140 | // ***** blinkLED ******************************************* 141 | // 142 | // toggles the monitor LED on and off as an indicator 143 | 144 | void blinkLED() { 145 | if ( monitorstate == LOW ) { 146 | monitorstate = HIGH; 147 | } else { 148 | monitorstate = LOW; 149 | } 150 | digitalWrite(MONITOR_PIN, monitorstate); 151 | } 152 | 153 | // ***** setup ******************************************* 154 | // 155 | // initializes Ethernet and opens the UDP port 156 | // it also sends an Art-Net Poll Reply packet telling other 157 | // Art-Net devices that it can transmit DMX from the network 158 | 159 | void setup() { 160 | pinMode(MONITOR_PIN, OUTPUT); //status LED 161 | blinkLED(); 162 | #if defined(SDSELECT_PIN) 163 | pinMode(SDSELECT_PIN, OUTPUT); 164 | #endif 165 | 166 | if ( USE_DHCP ) { // Initialize Ethernet 167 | Ethernet.begin(mac); // DHCP 168 | } else { 169 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 170 | } 171 | 172 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 173 | interface = new LXSACN(); 174 | //interface->setUniverse(1); // for different universe, change this line and the multicast address below 175 | } else { 176 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask()); 177 | use_multicast = 0; 178 | //((LXArtNet*)interface)->setSubnetUniverse(0, 0); //for different subnet/universe, change this line 179 | } 180 | 181 | if ( use_multicast ) { // Start listening for UDP on port 182 | eUDP.beginMulticast(IPAddress(239,255,0,1), interface->dmxPort()); 183 | } else { 184 | eUDP.begin(interface->dmxPort()); 185 | } 186 | 187 | SAMD21DMX.setDirectionPin(RXTX_PIN); 188 | SAMD21DMX.startOutput(); 189 | 190 | if ( ! USE_SACN ) { 191 | ((LXArtNet*)interface)->send_art_poll_reply(&eUDP); 192 | } 193 | blinkLED(); 194 | } 195 | 196 | 197 | /************************************************************************ 198 | 199 | The main loop checks for and reads packets from UDP ethernet socket 200 | connection. When a packet is recieved, it is checked to see if 201 | it is valid Art-Net and the art DMXReceived function is called, sending 202 | the DMX values to the output. 203 | 204 | *************************************************************************/ 205 | 206 | void loop() { 207 | uint8_t result = interface->readDMXPacket(&eUDP); 208 | 209 | if ( result == RESULT_DMX_RECEIVED ) { 210 | for (int i = 1; i <= interface->numberOfSlots(); i++) { 211 | SAMD21DMX.setSlot(i , interface->getSlot(i)); 212 | } 213 | blinkLED(); 214 | } 215 | } 216 | 217 | /* 218 | Revision History 219 | v1.0 modified AVR example for SAM 220 | */ 221 | -------------------------------------------------------------------------------- /examples/DMXOutputSAMD21/LXSAMD21DMX.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file LXSAMD21DMX.cpp 4 | @author Claude Heintz 5 | @license BSD (see SAMD21DMX.h or http://lx.claudeheintzdesign.com/opensource.html) 6 | @copyright 2016 by Claude Heintz 7 | 8 | DMX Driver for Arduino MKR1000 9 | 10 | @section HISTORY 11 | 12 | v1.0 - First release 13 | */ 14 | /**************************************************************************/ 15 | 16 | #include "LXSAMD21DMX.h" 17 | #include "Arduino.h" 18 | #include "wiring_private.h" 19 | #include "variant.h" 20 | #include 21 | #include 22 | 23 | //************************************************************************************** 24 | 25 | LXSAMD21DMX SAMD21DMX; 26 | 27 | // **************************** SERCOMn_Handler *************** 28 | 29 | void SERCOM4_Handler() 30 | { 31 | SAMD21DMX.IrqHandler(); 32 | } 33 | 34 | 35 | // ***************** define registers, flags and interrupts **************** 36 | 37 | //***** baud rate defines 38 | #define DMX_DATA_BAUD 250000 39 | #define DMX_BREAK_BAUD 90000 40 | //99900 41 | 42 | //***** states indicate current position in DMX stream 43 | #define DMX_STATE_BREAK 0 44 | #define DMX_STATE_START 1 45 | #define DMX_STATE_DATA 2 46 | #define DMX_STATE_IDLE 3 47 | 48 | //***** status is if interrupts are enabled and IO is active 49 | #define ISR_DISABLED 0 50 | #define ISR_OUTPUT_ENABLED 1 51 | #define ISR_INPUT_ENABLED 2 52 | 53 | // **************************** global data (can be accessed in ISR) *************** 54 | 55 | Uart SerialDMX (&DMX_sercom, PIN_DMX_RX, PIN_DMX_TX, SERCOM_RX_PAD_3, UART_TX_PAD_2); 56 | 57 | uint8_t* _shared_dmx_data; 58 | uint8_t _shared_dmx_state; 59 | uint16_t _shared_dmx_slot; 60 | uint16_t _shared_max_slots = DMX_MIN_SLOTS; 61 | LXRecvCallback _shared_receive_callback = NULL; 62 | 63 | 64 | void setBaudRate(uint32_t baudrate) { 65 | DMX_SERCOM->USART.CTRLA.bit.ENABLE = 0x0u; // must be disabled before writing to USART.BAUD or USART.CTRLA 66 | uint16_t sampleRateValue = 16; 67 | 68 | // see SERCOM.initUART 69 | uint32_t baudTimes8 = (SystemCoreClock * 8) / (sampleRateValue * baudrate); 70 | 71 | DMX_SERCOM->USART.BAUD.FRAC.FP = (baudTimes8 % 8); 72 | DMX_SERCOM->USART.BAUD.FRAC.BAUD = (baudTimes8 / 8); 73 | DMX_SERCOM->USART.CTRLA.bit.ENABLE = 0x1u; // re-enable 74 | } 75 | 76 | //************************************************************************************ 77 | // ************************ LXSAMD21DMXOutput member functions ******************** 78 | 79 | LXSAMD21DMX::LXSAMD21DMX ( void ) { 80 | _direction_pin = DIRECTION_PIN_NOT_USED; //optional 81 | _shared_max_slots = DMX_MAX_SLOTS; 82 | _interrupt_status = ISR_DISABLED; 83 | 84 | //zero buffer including _dmxData[0] which is start code 85 | memset(_dmxData, 0, DMX_MAX_SLOTS+1); 86 | } 87 | 88 | 89 | 90 | LXSAMD21DMX::~LXSAMD21DMX ( void ) { 91 | stop(); 92 | _shared_dmx_data = NULL; 93 | _shared_receive_callback = NULL; 94 | } 95 | 96 | 97 | void LXSAMD21DMX::startOutput ( void ) { 98 | if ( _direction_pin != DIRECTION_PIN_NOT_USED ) { 99 | digitalWrite(_direction_pin, HIGH); 100 | } 101 | 102 | if ( _interrupt_status == ISR_INPUT_ENABLED ) { 103 | stop(); 104 | } 105 | 106 | if ( _interrupt_status == ISR_DISABLED ) { //prevent messing up sequence if already started... 107 | SerialDMX.begin(DMX_BREAK_BAUD, (uint8_t)SERIAL_8N2); 108 | 109 | // Assign pin mux to SERCOM functionality (must come after SerialDMX.begin) 110 | pinPeripheral(PIN_DMX_RX, PIO_SERCOM_ALT); 111 | pinPeripheral(PIN_DMX_TX, PIO_SERCOM_ALT); 112 | 113 | _interrupt_status = ISR_OUTPUT_ENABLED; 114 | _shared_dmx_data = dmxData(); 115 | _shared_dmx_slot = 0; 116 | _shared_dmx_state = DMX_STATE_START; 117 | 118 | SERCOM4->USART.INTENSET.reg = SERCOM_USART_INTENSET_TXC | SERCOM_USART_INTENSET_ERROR; 119 | SERCOM4->USART.DATA.reg = 0; 120 | 121 | } 122 | } 123 | 124 | void LXSAMD21DMX::startInput ( void ) { 125 | if ( _direction_pin != DIRECTION_PIN_NOT_USED ) { 126 | digitalWrite(_direction_pin, LOW); 127 | } 128 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 129 | stop(); 130 | } 131 | if ( _interrupt_status == ISR_DISABLED ) { //prevent messing up sequence if already started... 132 | SerialDMX.begin(DMX_DATA_BAUD, (uint8_t)SERIAL_8N2); 133 | 134 | // Assign pin mux to SERCOM functionality (must come after SerialDMX.begin) 135 | pinPeripheral(PIN_DMX_RX, PIO_SERCOM_ALT); 136 | pinPeripheral(PIN_DMX_TX, PIO_SERCOM_ALT); 137 | 138 | _shared_dmx_data = dmxData(); 139 | _shared_dmx_slot = 0; 140 | _shared_dmx_state = DMX_STATE_IDLE; 141 | 142 | _interrupt_status = ISR_INPUT_ENABLED; 143 | } 144 | } 145 | 146 | void LXSAMD21DMX::stop ( void ) { 147 | SerialDMX.end(); 148 | _interrupt_status = ISR_DISABLED; 149 | } 150 | 151 | void LXSAMD21DMX::setDirectionPin( uint8_t pin ) { 152 | _direction_pin = pin; 153 | pinMode(_direction_pin, OUTPUT); 154 | } 155 | 156 | void LXSAMD21DMX::setMaxSlots (int slots) { 157 | if ( slots > DMX_MIN_SLOTS ) { 158 | _shared_max_slots = slots; 159 | } else { 160 | _shared_max_slots = DMX_MIN_SLOTS; 161 | } 162 | } 163 | 164 | uint8_t LXSAMD21DMX::getSlot (int slot) { 165 | return _dmxData[slot]; 166 | } 167 | 168 | void LXSAMD21DMX::setSlot (int slot, uint8_t value) { 169 | _dmxData[slot] = value; 170 | } 171 | 172 | uint8_t* LXSAMD21DMX::dmxData(void) { 173 | return &_dmxData[0]; 174 | } 175 | 176 | void LXSAMD21DMX::setDataReceivedCallback(LXRecvCallback callback) { 177 | _shared_receive_callback = callback; 178 | } 179 | 180 | 181 | void LXSAMD21DMX::IrqHandler(void) { 182 | 183 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 184 | 185 | if ( DMX_SERCOM->USART.INTFLAG.bit.TXC ) { 186 | if ( DMX_SERCOM->USART.INTFLAG.bit.DRE ) { 187 | 188 | if ( _shared_dmx_state == DMX_STATE_DATA ) { 189 | DMX_SERCOM->USART.DATA.reg = _shared_dmx_data[_shared_dmx_slot++]; //send next slot; 190 | if ( _shared_dmx_slot > _shared_max_slots ) { 191 | _shared_dmx_state = DMX_STATE_BREAK; 192 | } 193 | } else if ( _shared_dmx_state == DMX_STATE_BREAK ) { 194 | setBaudRate(DMX_BREAK_BAUD); 195 | _shared_dmx_state = DMX_STATE_START; 196 | _shared_dmx_slot = 0; 197 | DMX_SERCOM->USART.DATA.reg = 0; //break 198 | } else if ( _shared_dmx_state == DMX_STATE_START ) { 199 | setBaudRate(DMX_DATA_BAUD); 200 | _shared_dmx_state = DMX_STATE_DATA; 201 | DMX_SERCOM->USART.DATA.reg = _shared_dmx_data[_shared_dmx_slot++]; 202 | } 203 | 204 | } //DRE 205 | } //TXC 206 | 207 | } else if ( _interrupt_status == ISR_INPUT_ENABLED ) { 208 | 209 | if ( DMX_SERCOM->USART.INTFLAG.bit.ERROR ) { 210 | DMX_SERCOM->USART.INTFLAG.bit.ERROR = 1; //acknowledge error, clear interrupt 211 | 212 | if ( DMX_SERCOM->USART.STATUS.bit.FERR ) { //framing error happens when break is sent 213 | _shared_dmx_state = DMX_STATE_BREAK; 214 | if ( _shared_dmx_slot > 0 ) { 215 | if ( _shared_receive_callback != NULL ) { 216 | _shared_receive_callback(_shared_dmx_slot); 217 | } 218 | } 219 | _shared_dmx_slot = 0; 220 | return; 221 | } 222 | // other error flags? 223 | //return;? 224 | } //ERR 225 | 226 | if ( DMX_SERCOM->USART.INTFLAG.bit.RXC ) { 227 | uint8_t incoming_byte = DMX_SERCOM->USART.DATA.reg; // read buffer to clear interrupt flag 228 | switch ( _shared_dmx_state ) { 229 | case DMX_STATE_BREAK: 230 | if ( incoming_byte == 0 ) { // start code == zero (DMX) 231 | _shared_dmx_data[_shared_dmx_slot] = incoming_byte; 232 | _shared_dmx_state = DMX_STATE_DATA; 233 | } else { 234 | _shared_dmx_state = DMX_STATE_IDLE; 235 | } 236 | break; 237 | 238 | case DMX_STATE_DATA: 239 | _shared_dmx_data[_shared_dmx_slot++] = incoming_byte; // increments BEFORE assignment 240 | if ( _shared_dmx_slot > DMX_MAX_SLOTS ) { 241 | _shared_dmx_state = DMX_STATE_IDLE; // go to idle, wait for next break 242 | } 243 | break; 244 | } 245 | } // RXC 246 | 247 | } // input enabled 248 | 249 | } //--IrqHandler(void) 250 | -------------------------------------------------------------------------------- /examples/DMXOutputSAMD21/LXSAMD21DMX.h: -------------------------------------------------------------------------------- 1 | /* LXSAMD21DMX.h 2 | Copyright 2016 by Claude Heintz Design 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of LXSAMD21DMX nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | ----------------------------------------------------------------------------------- 30 | 31 | The LXSAMD21DMX library supports output and input of DMX using 32 | sercom4 of a MKR1000 microcontroller in UART mode. (pins 7 & 8) 33 | 34 | This is the circuit for a simple unisolated DMX Shield 35 | that could be used with LXSAMD21DMX. It uses a line driver IC 36 | to convert the output from the SAM D-21 to DMX: 37 | 38 | MKR1000 Pin 39 | | SN 75176 A or MAX 481CPA 40 | V _______________ 41 | | | 1 Vcc 8 |------(+5v) 42 | RX (5) |----------------------| | DMX Output 43 | | +----| 2 B 7 |---------------- Pin 2 44 | | | | | 45 | (3) |----------------------| 3 DE A 6 |---------------- Pin 3 46 | | | | 47 | TX (4) |----------------------| 4 DI Gnd 5 |---+------------ Pin 1 48 | | | 49 | | (GND) 50 | 51 | Data Enable (DE) and Inverted Read Enable (!RE) can be wired to +5v for output or Gnd for input 52 | if direction switching is not needed. 53 | */ 54 | 55 | #ifndef LXSAM21_DMX_H 56 | #define LXSAM21_DMX_H 57 | 58 | #include 59 | #include "SERCOM.h" 60 | 61 | #define DMX_MIN_SLOTS 24 62 | #define DMX_MAX_SLOTS 512 63 | 64 | #define DIRECTION_PIN_NOT_USED 255 65 | 66 | typedef void (*LXRecvCallback)(int); 67 | 68 | /*! 69 | @class LXSAMD21DMX 70 | @abstract 71 | LXSAMD21DMX is a driver for sending or receiving DMX using one of a SAM D-21's five serial peripheral interfaces (SERCOMs). 72 | 73 | LXSAMD21DMX output mode continuously sends DMX once its interrupts have been enabled using startOutput(). 74 | Use setSlot() to set the level value for a particular DMX dimmer/address/channel. 75 | 76 | LXSAMD21DMX input mode continuously receives DMX once its interrupts have been enabled using startInput() 77 | Use getSlot() to read the level value for a particular DMX dimmer/address/channel. 78 | 79 | LXSAMD21DMX is used with a single instance called SAMD21DMX . 80 | */ 81 | 82 | class LXSAMD21DMX { 83 | 84 | public: 85 | 86 | LXSAMD21DMX ( void ); 87 | ~LXSAMD21DMX ( void ); 88 | 89 | /*! 90 | * @brief starts interrupt that continuously sends DMX output 91 | * @discussion Sets up baud rate, bits and parity, 92 | * sets globals accessed in ISR, 93 | * enables transmission (TE) and tx interrupts (TIE/TCIE). 94 | */ 95 | void startOutput( void ); 96 | 97 | /*! 98 | * @brief starts interrupt that continuously reads DMX data 99 | * @discussion sets up baud rate, bits and parity, 100 | * sets globals accessed in ISR, 101 | * enables receive (RE) and rx interrupt (RIE) 102 | */ 103 | void startInput( void ); 104 | 105 | /*! 106 | * @brief disables tx, rx and interrupts. 107 | */ 108 | void stop( void ); 109 | 110 | /*! 111 | * @brief optional utility sets the pin used to control driver chip's 112 | * DE (data enable) line, HIGH for output, LOW for input. 113 | * @param pin to be automatically set for input/output direction 114 | */ 115 | void setDirectionPin( uint8_t pin ); 116 | 117 | /*! 118 | * @brief Sets the number of slots (aka addresses or channels) sent per DMX frame. 119 | * @discussion defaults to 512 or DMX_MAX_SLOTS and should be no less DMX_MIN_SLOTS slots. 120 | * The DMX standard specifies min break to break time no less than 1024 usecs. 121 | * At 44 usecs per slot ~= 24 122 | * @param slot the highest slot number (~24 to 512) 123 | */ 124 | void setMaxSlots (int slot); 125 | 126 | /*! 127 | * @brief reads the value of a slot/address/channel 128 | * @discussion NOTE: Data is not double buffered. 129 | * So a complete single frame is not guaranteed. 130 | * The ISR continuously reads the next frame into the buffer 131 | * @return level (0-255) 132 | */ 133 | uint8_t getSlot (int slot); 134 | 135 | /*! 136 | * @brief Sets the output value of a slot 137 | * @param slot number of the slot/address/channel (1-512) 138 | * @param value level (0-255) 139 | */ 140 | void setSlot (int slot, uint8_t value); 141 | 142 | /*! 143 | * @brief provides direct access to data array 144 | * @return pointer to dmx array 145 | */ 146 | uint8_t* dmxData(void); 147 | 148 | /*! 149 | * @brief Function called when DMX frame has been read 150 | * @discussion Sets a pointer to a function that is called 151 | * on the break after a DMX frame has been received. 152 | * Whatever happens in this function should be quick! 153 | * Best used to set a flag that is polled outside of ISR for available data. 154 | */ 155 | void setDataReceivedCallback(LXRecvCallback callback); 156 | 157 | /*! 158 | * @brief interrupt handler function 159 | */ 160 | void IrqHandler(); 161 | 162 | private: 163 | /*! 164 | * @brief Indicates mode ISR_OUTPUT_ENABLED or ISR_INPUT_ENABLED or ISR_DISABLED 165 | */ 166 | uint8_t _interrupt_status; 167 | 168 | /*! 169 | * @brief pin used to control direction of output driver chip 170 | */ 171 | uint8_t _direction_pin; 172 | 173 | /*! 174 | * @brief Array of dmx data including start code 175 | */ 176 | uint8_t _dmxData[DMX_MAX_SLOTS+1]; 177 | 178 | }; 179 | 180 | extern LXSAMD21DMX SAMD21DMX; 181 | 182 | /************************** 183 | Change the following lines to use other than SERCOM4. 184 | The following table shows the MKR1000 default uses of the SERCOMs 185 | and their associated pins: 186 | 187 | SERCOM0 Wire 11 12 188 | SERCOM1 SPI 8 9 10 189 | SERCOM2 ATWINC1501B SPI 190 | SERCOM3 191 | SERCOM4 192 | SERCOM5 Serial1 13 14 193 | 194 | Changing the SERCOM also requires changing the pin MUX. 195 | The following are MUX options for SERCOM3 and SERCOM4 196 | Mkr1000 pins 197 | 198 | Sercom 3 199 | 00 S/0 200 | 01 S/1 201 | 06 SA/2 202 | 07 SA/3 203 | 08 SA/0 204 | 09 SA/1 205 | 10 SA/3 206 | 207 | Sercom 4 208 | 04 SA/2 209 | 05 SA/3 210 | 211 | see datasheet pg 33 for sercom4 mux options 212 | Need to be PB10 PB11, pad 2, pad 3 pad3=rx=3, pad 2=tx=1 213 | which correspond to MKR10000 pins 4 & 5. 214 | 215 | In general, tx is either pad 0 = CRegA_TXPO 0x0 or pad 2 = CRegA_TXPO 0x1 216 | see datasheet pg 481 for control register A 217 | 218 | NOTE: You MUST change the name of the SERCOMn_Handler() function in LXSAMD21DMX.cpp 219 | if you use a different SERCOM 220 | */ 221 | 222 | #define PIN_DMX_RX (5ul) 223 | #define PIN_DMX_TX (4ul) 224 | #define PAD_DMX_RX SERCOM_RX_PAD_3 225 | #define PAD_DMX_TX UART_TX_PAD_2 226 | 227 | // SERCOMn is pointer to memory address where SERCOM registers are located. 228 | #define DMX_SERCOM SERCOM4 229 | // sercomN is C++ wrapper for SERCOMn (passed to UART constructor) 230 | #define DMX_sercom sercom4 231 | 232 | #endif // ifndef LXSAM21_DMX_H 233 | -------------------------------------------------------------------------------- /examples/DMXOutputUsingUSART/DMXOutputUsingUSART.ino: -------------------------------------------------------------------------------- 1 | /* 2 | DMXOutputUsingUSART.ino 3 | 4 | This sketch demonstrates converting Art-Net or sACN packets recieved using an 5 | Arduino Ethernet Shield to DMX output using serial output to a driver chip. 6 | 7 | This sketch requires software such as LXConsole, QLC+ or DMXWorkshop 8 | that can send Art-Net or sACN packets to the Arduino. 9 | 10 | The sketch receives incoming UDP packets. If a received packet is 11 | Art-Net/sACN DMX output, it uses the AVR microcontroller's USART to output 12 | the dmx levels as serial data. A driver chip is used 13 | to convert this output to a differential signal (actual DMX). 14 | 15 | This is the circuit for a simple unisolated DMX Shield: 16 | 17 | Arduino SN 75176 A or MAX 481CPA 18 | pin _______________ 19 | | | 1 Vcc 8 |------ +5v 20 | V | | DMX Output 21 | | +----| 2 B 7 |---------------- Pin 2 22 | | | | | 23 | 2 |----------------------| 3 DE A 6 |---------------- Pin 3 24 | | | | 25 | TX |----------------------| 4 DI Gnd 5 |---+------------ Pin 1 26 | | | 27 | | GND 28 | 5 |--------[ 330 ohm ]---[ LED ]------------| 29 | 30 | 31 | 32 | Created January 7th, 2014 by Claude Heintz 33 | Current version 1.5 34 | (see bottom of file for revision history) 35 | 36 | See LXArduinoDMXUSART.h or http://lx.claudeheintzdesign.com/opensource.html for license. 37 | Art-Net(tm) Designed by and Copyright Artistic Licence Holdings Ltd. 38 | 39 | */ 40 | 41 | //*********************** includes *********************** 42 | 43 | #include "LXArduinoDMXUSART.h" 44 | #include 45 | 46 | /******** Important note about Ethernet library ******** 47 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 48 | It is necessary to use an Ethernet library that supports the correct chip for your shield 49 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 50 | 51 | https://github.com/PaulStoffregen/Ethernet 52 | 53 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 54 | neccessary if the shield receives more than a single universe of packets. 55 | 56 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 57 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 58 | uncomment the line to define ETHERNET_SHIELD_V2 59 | */ 60 | #define ETHERNET_SHIELD_V2 1 61 | 62 | #if defined ( ETHERNET_SHIELD_V2 ) 63 | #include 64 | #include 65 | #else 66 | #include 67 | #include 68 | #endif 69 | 70 | #include 71 | #include 72 | #include 73 | 74 | //*********************** defines *********************** 75 | 76 | /* 77 | Enter a MAC address and IP address for your controller below. 78 | The MAC address can be random as is the one shown, but should 79 | not match any other MAC address on your network. 80 | 81 | If BROADCAST_IP is not defined, ArtPollReply will be sent directly to server 82 | rather than being broadcast. (If a server' socket is bound to a specific network 83 | interface ip address, it will not receive broadcast packets.) 84 | */ 85 | 86 | #define USE_DHCP 1 87 | #define USE_SACN 0 88 | #define ARTNET_NODE_NAME "my Art-Net node" 89 | 90 | // Uncomment to use multicast, which requires extended Ethernet library 91 | // see note in LXDMXEthernet.h file about method added to library 92 | 93 | //#define USE_MULTICAST 1 94 | 95 | #define MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x0F, 0xF7, 0x61 96 | #define IP_ADDRESS 192,168,1,20 97 | #define GATEWAY_IP 192,168,1,1 98 | #define SUBNET_MASK 255,255,255,0 99 | #define BROADCAST_IP 192,168,1,255 100 | 101 | // this sketch flashes an indicator led: 102 | #define MONITOR_PIN 5 103 | 104 | // the driver direction is controlled by: 105 | #define RXTX_PIN 2 106 | 107 | //the Ethernet Shield has an SD card that also communicates by SPI 108 | //set its select pin to output to be safe: 109 | #define SDSELECT_PIN 4 110 | 111 | 112 | //*********************** globals *********************** 113 | 114 | //network addresses 115 | byte mac[] = { MAC_ADDRESS }; 116 | IPAddress ip(IP_ADDRESS); 117 | IPAddress gateway(GATEWAY_IP); 118 | IPAddress subnet_mask(SUBNET_MASK); 119 | 120 | #if defined( BROADCAST_IP ) 121 | IPAddress broadcast_ip( BROADCAST_IP); 122 | #else 123 | IPAddress broadcast_ip = INADDR_NONE; 124 | #endif 125 | 126 | // buffer 127 | unsigned char packetBuffer[ARTNET_BUFFER_MAX]; 128 | 129 | // An EthernetUDP instance to let us send and receive packets over UDP 130 | EthernetUDP eUDP; 131 | 132 | // LXDMXEthernet instance ( created in setup so its possible to get IP if DHCP is used ) 133 | LXDMXEthernet* interface; 134 | 135 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 136 | #if defined ( USE_MULTICAST ) 137 | uint8_t use_multicast = USE_SACN; 138 | #else 139 | uint8_t use_multicast = 0; 140 | #endif 141 | 142 | // used to toggle on and off the LED when DMX is Received 143 | int monitorstate = LOW; 144 | 145 | 146 | /* setup initializes Ethernet and opens the UDP port 147 | it also sends an Art-Net Poll Reply packet telling other 148 | Art-Net devices that it can transmit DMX from the network */ 149 | 150 | // ***** blinkLED ******************************************* 151 | // 152 | // toggles the monitor LED on and off as an indicator 153 | 154 | void blinkLED() { 155 | if ( monitorstate == LOW ) { 156 | monitorstate = HIGH; 157 | } else { 158 | monitorstate = LOW; 159 | } 160 | digitalWrite(MONITOR_PIN, monitorstate); 161 | } 162 | 163 | // ***** setup ******************************************* 164 | // 165 | // initializes Ethernet and opens the UDP port 166 | // it also sends an Art-Net Poll Reply packet telling other 167 | // Art-Net devices that it can transmit DMX from the network 168 | 169 | void setup() { 170 | pinMode(MONITOR_PIN, OUTPUT); //status LED 171 | blinkLED(); 172 | #if defined(SDSELECT_PIN) 173 | pinMode(SDSELECT_PIN, OUTPUT); 174 | #endif 175 | 176 | if ( USE_DHCP ) { // Initialize Ethernet 177 | Ethernet.begin(mac); // DHCP 178 | } else { 179 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 180 | } 181 | 182 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 183 | interface = new LXSACN(); 184 | //interface->setUniverse(1); // for different universe, change this line and the multicast address below 185 | } else { 186 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask()); 187 | use_multicast = 0; 188 | //((LXArtNet*)interface)->setSubnetUniverse(0, 0); //for different subnet/universe, change this line 189 | } 190 | 191 | if ( use_multicast ) { // Start listening for UDP on port 192 | #if defined ( USE_MULTICAST ) 193 | eUDP.beginMulticast(IPAddress(239,255,0,1), interface->dmxPort()); 194 | #endif 195 | } else { 196 | eUDP.begin(interface->dmxPort()); 197 | } 198 | 199 | LXSerialDMX.setDirectionPin(RXTX_PIN); 200 | LXSerialDMX.startOutput(); 201 | 202 | if ( ! USE_SACN ) { 203 | ((LXArtNet*)interface)->setNodeName(ARTNET_NODE_NAME); 204 | ((LXArtNet*)interface)->send_art_poll_reply(&eUDP); 205 | } 206 | blinkLED(); 207 | } 208 | 209 | 210 | /************************************************************************ 211 | 212 | The main loop checks for and reads packets from UDP ethernet socket 213 | connection. When a packet is recieved, it is checked to see if 214 | it is valid Art-Net and the art DMXReceived function is called, sending 215 | the DMX values to the output. 216 | 217 | *************************************************************************/ 218 | 219 | void loop() { 220 | if ( USE_DHCP ) { 221 | uint8_t dhcpr = Ethernet.maintain(); 222 | if (( dhcpr == 4 ) || (dhcpr == 2)) { //renew/rebind success 223 | if ( ! USE_SACN ) { 224 | ((LXArtNet*)interface)->setLocalIP(Ethernet.localIP(), Ethernet.subnetMask()); 225 | } 226 | } 227 | } 228 | 229 | uint8_t result = interface->readDMXPacket(&eUDP); 230 | 231 | if ( result == RESULT_DMX_RECEIVED ) { 232 | for (int i = 1; i <= interface->numberOfSlots(); i++) { 233 | LXSerialDMX.setSlot(i , interface->getSlot(i)); 234 | } 235 | blinkLED(); 236 | } 237 | } 238 | 239 | /* 240 | Revision History 241 | v1.0 initial release January 2014 242 | v1.1 added monitor LED to code and circuit 243 | v1.2 8/14/15 changed library support and clarified code 244 | v1.3 moved control of options to "includes" and "defines" 245 | v1.4 uses LXArtNet class which encapsulates Art-Net functionality 246 | v1.5 revised as example file for library 247 | */ 248 | -------------------------------------------------------------------------------- /examples/DMXOutputUsingUSART/LXArduinoDMXUSART.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file LXUSARTDMX.cpp 4 | @author Claude Heintz 5 | @license BSD (see LXUSARTDMX.h) 6 | @copyright 2015-2016 by Claude Heintz 7 | 8 | DMX Driver for Arduino using USART. 9 | 10 | @section HISTORY 11 | 12 | v1.0 First release 13 | v1.1 - Consolidated input and output into single class 14 | */ 15 | /**************************************************************************/ 16 | #include "pins_arduino.h" 17 | #include "LXArduinoDMXUSART.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | LXUSARTDMX LXSerialDMX; 24 | 25 | // ***************** define registers, flags and interrupts **************** 26 | 27 | // processors with single UART such AT328 processor (Uno) 28 | // should not define USE_UART_1 29 | // Uncomment the following to use UART1: 30 | //#define USE_UART_1 31 | 32 | // USE_UART_1 should be defined for Teensy 2.0++, Leonardo, Yun 33 | #if defined(__AVR_AT90USB1286__) || defined (__AVR_ATmega32U4__) 34 | #define USE_UART_1 35 | #endif 36 | 37 | 38 | #if !defined(USE_UART_1) // definitions for UART0 39 | // for AT328 see datasheet pg 194 40 | 41 | #define LXUCSRA UCSR0A // USART register A 42 | #define LXTXC TXC0 // tx buffer empty 43 | #define LXUDRE UDRE0 // data ready 44 | #define LXFE FE0 // frame error 45 | #define LXU2X U2X0 // double speed 46 | 47 | #define LXUCSRB UCSR0B // USART register B 48 | #define LXRXCIE RXCIE0 // rx interrupt enable bit 49 | #define LXTXCIE TXCIE0 // tx interrupt enable bit 50 | #define LXRXEN RXEN0 // rx enable bit 51 | #define LXTXEN TXEN0 // tx enable bit 52 | 53 | #define LXUCSRC UCSR0C // USART register C 54 | #define LXUSBS0 USBS0 // stop bits 55 | #define LXUCSZ0 UCSZ00 // length 56 | #define LXUPM0 UPM00 // parity 57 | #define LXUCSRRH UBRR0H // USART baud rate register msb 58 | #define LXUCSRRL UBRR0L // USARTbaud rate register msb 59 | #define LXUDR UDR0 // USART data register tx/rx 60 | 61 | #define LXUSART_RX_vect USART_RX_vect // RX ISR 62 | #define LXUSART_TX_vect USART_TX_vect // TX ISR 63 | 64 | #define BIT_FRAME_ERROR (1<>8); 173 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 174 | LXUCSRA &= ~BIT_2X_SPEED; 175 | 176 | LXUDR = 0x0; //USART send register 177 | _shared_dmx_data = dmxData(); 178 | _shared_dmx_state = DMX_STATE_BREAK; 179 | 180 | LXUCSRC = FORMAT_8N2; //set length && stopbits (no parity) 181 | LXUCSRB |= BIT_TX_ENABLE | BIT_TX_ISR_ENABLE; //enable tx and tx interrupt 182 | _interrupt_status = ISR_OUTPUT_ENABLED; 183 | } 184 | } 185 | 186 | // ***** start ***** 187 | // sets up baud rate, bits and parity 188 | // sets globals accessed in ISR 189 | // enables transmission and tx interrupt 190 | 191 | void LXUSARTDMX::startInput ( void ) { 192 | if ( _direction_pin != DIRECTION_PIN_NOT_USED ) { 193 | digitalWrite(_direction_pin, LOW); 194 | } 195 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 196 | stop(); 197 | } 198 | if ( _interrupt_status == ISR_DISABLED ) { //prevent messing up sequence if already started... 199 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1)>>8); 200 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 201 | LXUCSRA &= ~BIT_2X_SPEED; 202 | 203 | _shared_dmx_data = dmxData(); 204 | _shared_dmx_state = DMX_STATE_IDLE; 205 | _shared_dmx_slot = 0; 206 | 207 | LXUCSRC = FORMAT_8N2; //set length && stopbits (no parity) 208 | LXUCSRB |= BIT_RX_ENABLE | BIT_RX_ISR_ENABLE; //enable tx and tx interrupt 209 | _interrupt_status = ISR_INPUT_ENABLED; 210 | } 211 | } 212 | 213 | // ***** stop ***** 214 | // disables interrupts 215 | 216 | void LXUSARTDMX::stop ( void ) { 217 | if ( _interrupt_status == ISR_OUTPUT_ENABLED ) { 218 | LXUCSRB &= ~BIT_TX_ISR_ENABLE; //disable tx interrupt 219 | LXUCSRB &= ~BIT_TX_ENABLE; //disable tx enable 220 | } else if ( _interrupt_status == ISR_INPUT_ENABLED ) { 221 | LXUCSRB &= ~BIT_RX_ISR_ENABLE; //disable rx interrupt 222 | LXUCSRB &= ~BIT_RX_ENABLE; //disable rx enable 223 | } 224 | _interrupt_status = ISR_DISABLED; 225 | } 226 | 227 | void LXUSARTDMX::setDirectionPin( uint8_t pin ) { 228 | _direction_pin = pin; 229 | pinMode(_direction_pin, OUTPUT); 230 | } 231 | 232 | // ***** setMaxSlots ***** 233 | // sets the number of slots sent per DMX frame 234 | // defaults to 512 or DMX_MAX_SLOTS 235 | // should be no less DMX_MIN_SLOTS slots 236 | // the DMX standard specifies min break to break time no less than 1024 usecs 237 | // at 44 usecs per slot ~= 24 238 | 239 | void LXUSARTDMX::setMaxSlots (int slots) { 240 | _shared_max_slots = max(slots, DMX_MIN_SLOTS); 241 | } 242 | 243 | // ***** getSlot ***** 244 | // reads the value of a slot 245 | // see buffering note for ISR below 246 | 247 | uint8_t LXUSARTDMX::getSlot (int slot) { 248 | return _dmxData[slot]; 249 | } 250 | 251 | // ***** setSlot ***** 252 | // sets the output value of a slot 253 | 254 | void LXUSARTDMX::setSlot (int slot, uint8_t value) { 255 | _dmxData[slot] = value; 256 | } 257 | 258 | // ***** dmxData ***** 259 | // pointer to data buffer 260 | 261 | uint8_t* LXUSARTDMX::dmxData(void) { 262 | return &_dmxData[0]; 263 | } 264 | 265 | // ***** setDataReceivedCallback ***** 266 | // sets pointer to function that is called 267 | // on the break after a frame has been received 268 | // whatever happens in this function should be quick 269 | // ie set a flag so that processing of the received data happens 270 | // outside of the ISR. 271 | 272 | 273 | void LXUSARTDMX::setDataReceivedCallback(LXRecvCallback callback) { 274 | _shared_receive_callback = callback; 275 | } 276 | 277 | 278 | //************************************************************************************ 279 | // ************************ TX ISR (transmit interrupt service routine) ************* 280 | // 281 | // this routine is called when USART transmission is complete 282 | // what this does is to send the next byte 283 | // when that byte is done being sent, the ISR is called again 284 | // and the cycle repeats... 285 | // until _shared_max_slots worth of bytes have been sent on succesive triggers of the ISR 286 | // and then on the next ISR... 287 | // the break/mark after break is sent at a different speed 288 | // and then on the next ISR... 289 | // the start code is sent 290 | // and then on the next ISR... 291 | // the next data byte is sent 292 | // and the cycle repeats... 293 | 294 | 295 | ISR (LXUSART_TX_vect) { 296 | switch ( _shared_dmx_state ) { 297 | 298 | case DMX_STATE_BREAK: 299 | // set the slower baud rate and send the break 300 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_BREAK_BAUD * 8L) / (DMX_BREAK_BAUD * 16L) - 1)>>8); 301 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_BREAK_BAUD * 8L) / (DMX_BREAK_BAUD * 16L) - 1); 302 | LXUCSRA &= ~BIT_2X_SPEED; 303 | LXUCSRC = FORMAT_8E1; 304 | _shared_dmx_state = DMX_STATE_START; 305 | LXUDR = 0x0; 306 | break; // <- DMX_STATE_BREAK 307 | 308 | case DMX_STATE_START: 309 | // set the baud to full speed and send the start code 310 | LXUCSRRH = (unsigned char)(((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1)>>8); 311 | LXUCSRRL = (unsigned char) ((F_CLK + DMX_DATA_BAUD * 8L) / (DMX_DATA_BAUD * 16L) - 1); 312 | LXUCSRA &= ~BIT_2X_SPEED; 313 | LXUCSRC = FORMAT_8N2; 314 | _shared_dmx_slot = 0; 315 | LXUDR = _shared_dmx_data[_shared_dmx_slot++]; //send next slot (start code) 316 | _shared_dmx_state = DMX_STATE_DATA; 317 | break; // <- DMX_STATE_START 318 | 319 | case DMX_STATE_DATA: 320 | // send the next data byte until the end is reached 321 | LXUDR = _shared_dmx_data[_shared_dmx_slot++]; //send next slot 322 | if ( _shared_dmx_slot > _shared_max_slots ) { 323 | _shared_dmx_state = DMX_STATE_BREAK; 324 | } 325 | break; // <- DMX_STATE_DATA 326 | } 327 | } 328 | 329 | //*********************************************************************************** 330 | // ************************ RX ISR (receive interrupt service routine) ************* 331 | // 332 | // this routine is called when USART receives data 333 | // wait for break: if have previously read data call callback function 334 | // then on next receive: check start code 335 | // then on next receive: read data until done (in which case idle) 336 | // 337 | // NOTE: data is not double buffered 338 | // so a complete single frame is not guaranteed 339 | // the ISR will continue to read the next frame into the buffer 340 | 341 | ISR (LXUSART_RX_vect) { 342 | uint8_t status_register = LXUCSRA; 343 | uint8_t incoming_byte = LXUDR; 344 | 345 | if ( status_register & BIT_FRAME_ERROR ) { 346 | _shared_dmx_state = DMX_STATE_BREAK; 347 | if ( _shared_dmx_slot > 0 ) { 348 | if ( _shared_receive_callback != NULL ) { 349 | _shared_receive_callback(_shared_dmx_slot); 350 | } 351 | } 352 | _shared_dmx_slot = 0; 353 | return; 354 | } 355 | 356 | switch ( _shared_dmx_state ) { 357 | 358 | case DMX_STATE_BREAK: 359 | if ( incoming_byte == 0 ) { //start code == zero (DMX) 360 | _shared_dmx_state = DMX_STATE_DATA; 361 | _shared_dmx_slot = 1; 362 | } else { 363 | _shared_dmx_state = DMX_STATE_IDLE; 364 | } 365 | break; 366 | 367 | case DMX_STATE_DATA: 368 | _shared_dmx_data[_shared_dmx_slot++] = incoming_byte; 369 | if ( _shared_dmx_slot > DMX_MAX_SLOTS ) { 370 | _shared_dmx_state = DMX_STATE_IDLE; // go to idle, wait for next break 371 | } 372 | break; 373 | } 374 | } -------------------------------------------------------------------------------- /examples/DMXOutputUsingUSART/LXArduinoDMXUSART.h: -------------------------------------------------------------------------------- 1 | /* LXArduinoDMX.h 2 | Copyright 2015-2016 by Claude Heintz Design 3 | 4 | ----------------------------------- License ----------------------------------- 5 | LXArduinoDMX is free software: you can redistribute it and/or modify 6 | it for any purpose provided the following conditions are met: 7 | 8 | 1) Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2) Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3) Neither the name of the copyright owners nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | LXArduinoDMX is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 24 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 25 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 | HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ----------------------------------------------------------------------------------- 33 | 34 | The LXArduinoDMX library supports output and input of DMX using the USART 35 | serial output of an Arduino's microcontroller. With microcontrollers with a single 36 | USART such as the AT328 used by an Arduino Uno, this means that the Serial library 37 | conflicts with LXArduinoDMX and cannot be used. Other chips have more than one 38 | USART and can support both Serial and LXArduinoDMX by specifying that LXArduinoDMX uses 39 | USART1 instead of USART0. 40 | 41 | This is the circuit for a simple unisolated DMX Shield 42 | that could be used with LXArdunoDMX. It uses a line driver IC 43 | to convert the output from the Arduino to DMX: 44 | 45 | Arduino Pin 46 | | SN 75176 A or MAX 481CPA 47 | V _______________ 48 | | | 1 Vcc 8 |------(+5v) 49 | RX (0) |----------------------| | DMX Output 50 | | +----| 2 B 7 |---------------- Pin 2 51 | | | | | 52 | (2) |----------------------| 3 DE A 6 |---------------- Pin 3 53 | | | | 54 | TX (1) |----------------------| 4 DI Gnd 5 |---+------------ Pin 1 55 | | | 56 | | (GND) 57 | 58 | Data Enable (DE) and Inverted Read Enable (!RE) can be wired to +5v for output 59 | or Gnd for input, if direction switching is not needed. 60 | 61 | 62 | */ 63 | 64 | #ifndef LXusartDMX_H 65 | #define LXusartDMX_H 66 | 67 | #include 68 | #include 69 | 70 | #define DMX_MIN_SLOTS 24 71 | #define DMX_MAX_SLOTS 512 72 | 73 | #define DIRECTION_PIN_NOT_USED 255 74 | 75 | typedef void (*LXRecvCallback)(int); 76 | 77 | /*! 78 | @class LXUSARTDMX 79 | @abstract 80 | LXUSARTDMX is a driver for sending or receiving DMX using an AVR family microcontroller 81 | UART0 RX pin 0, TX pin 1 82 | 83 | LXUSARTDMX output mode continuously sends DMX once its interrupts have been enabled using startOutput(). 84 | Use setSlot() to set the level value for a particular DMX dimmer/address/channel. 85 | 86 | LXUSARTDMX input mode continuously receives DMX once its interrupts have been enabled using startInput() 87 | Use getSlot() to read the level value for a particular DMX dimmer/address/channel. 88 | 89 | LXUSARTDMX is used with a single instance called LXSerialDMX . 90 | */ 91 | 92 | class LXUSARTDMX { 93 | 94 | public: 95 | LXUSARTDMX ( void ); 96 | ~LXUSARTDMX ( void ); 97 | 98 | /*! 99 | * @brief starts interrupt that continuously sends DMX data 100 | * @discussion sets up baud rate, bits and parity, 101 | * sets globals accessed in ISR, 102 | * enables transmission and tx interrupt 103 | */ 104 | void startOutput( void ); 105 | 106 | /*! 107 | * @brief starts interrupt that continuously reads DMX data 108 | * @discussion sets up baud rate, bits and parity, 109 | * sets globals accessed in ISR, 110 | * enables receive and rx interrupt 111 | */ 112 | void startInput( void ); 113 | 114 | /*! 115 | * @brief disables USART 116 | */ 117 | void stop( void ); 118 | 119 | /*! 120 | * @brief optional utility sets the pin used to control driver chip's 121 | * DE (data enable) line, HIGH for output, LOW for input. 122 | * @param pin to be automatically set for input/output direction 123 | */ 124 | void setDirectionPin( uint8_t pin ); 125 | 126 | /*! 127 | * @brief Sets the number of slots (aka addresses or channels) sent per DMX frame. 128 | * @discussion defaults to 512 or DMX_MAX_SLOTS and should be no less DMX_MIN_SLOTS slots. 129 | * The DMX standard specifies min break to break time no less than 1024 usecs. 130 | * At 44 usecs per slot ~= 24 131 | * @param slot the highest slot number (~24 to 512) 132 | */ 133 | void setMaxSlots (int slot); 134 | 135 | /*! 136 | * @brief reads the value of a slot/address/channel 137 | * @discussion NOTE: Data is not double buffered. 138 | * So a complete single frame is not guaranteed. 139 | * The ISR continuously reads the next frame into the buffer 140 | * @return level (0-255) 141 | */ 142 | uint8_t getSlot (int slot); 143 | 144 | /*! 145 | * @brief Sets the output value of a slot 146 | * @param slot number of the slot/address/channel (1-512) 147 | * @param value level (0-255) 148 | */ 149 | void setSlot (int slot, uint8_t value); 150 | 151 | /*! 152 | * @brief provides direct access to data array 153 | * @return pointer to dmx array 154 | */ 155 | uint8_t* dmxData(void); 156 | 157 | /*! 158 | * @brief Function called when DMX frame has been read 159 | * @discussion Sets a pointer to a function that is called 160 | * on the break after a DMX frame has been received. 161 | * Whatever happens in this function should be quick! 162 | * Best used to set a flag that is polled outside of ISR for available data. 163 | */ 164 | void setDataReceivedCallback(LXRecvCallback callback); 165 | 166 | private: 167 | /*! 168 | * @brief Indicates mode ISR_OUTPUT_ENABLED or ISR_INPUT_ENABLED or ISR_DISABLED 169 | */ 170 | uint8_t _interrupt_status; 171 | 172 | /*! 173 | * @brief pin used to control direction of output driver chip 174 | */ 175 | uint8_t _direction_pin; 176 | 177 | /*! 178 | * @brief Array of dmx data including start code 179 | */ 180 | uint8_t _dmxData[DMX_MAX_SLOTS+1]; 181 | }; 182 | 183 | extern LXUSARTDMX LXSerialDMX; 184 | 185 | #endif // ifndef LXArduinoDMX_H -------------------------------------------------------------------------------- /examples/OutputToNetwork/OutputToNetwork.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file DMX2Ethernet.ino 4 | @author Claude Heintz 5 | @license BSD (see LXDMXEthernet.h) 6 | @copyright 2019 by Claude Heintz 7 | 8 | Example using LXDMXEthernet_Library for output from Arduino to Art-Net or E1.31 sACN 9 | 10 | Art-Net(TM) Designed by and Copyright Artistic Licence Holdings Ltd. 11 | sACN E 1.31 is a public standard published by the PLASA technical standards program 12 | 13 | See LXDMXEthernet.h or http://lx.claudeheintzdesign.com/opensource.html for license. 14 | 15 | @section HISTORY 16 | 17 | v1.00 - First release 18 | 19 | 20 | //*********************** includes ***********************/ 21 | 22 | #include 23 | 24 | /******** Important note about Ethernet library ******** 25 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 26 | It is necessary to use an Ethernet library that supports the correct chip for your shield 27 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 28 | 29 | https://github.com/PaulStoffregen/Ethernet 30 | 31 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 32 | neccessary if the shield receives more than a single universe of packets. 33 | 34 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 35 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 36 | uncomment the line to define ETHERNET_SHIELD_V2 37 | */ 38 | 39 | #define ETHERNET_SHIELD_V2 1 40 | 41 | #if defined ( ETHERNET_SHIELD_V2 ) 42 | #include 43 | #include 44 | #else 45 | #include 46 | #include 47 | #endif 48 | 49 | #include 50 | #include 51 | #include 52 | 53 | //*********************** defines *********************** 54 | 55 | /* 56 | Enter a MAC address and IP address for your controller below. 57 | The MAC address can be random as is the one shown, but should 58 | not match any other MAC address on your network. 59 | 60 | If BROADCAST_IP is not defined, ArtPollReply will be sent directly to server 61 | rather than being broadcast. (If a server' socket is bound to a specific network 62 | interface ip address, it will not receive broadcast packets.) 63 | */ 64 | 65 | #define USE_DHCP 1 66 | #define USE_SACN 1 67 | // comment out next line for unicast sACN 68 | #define USE_MULTICAST 1 69 | 70 | #define MAC_ADDRESS 0x90, 0xA2, 0xDA, 0x10, 0x6C, 0xA8 71 | #define IP_ADDRESS 192,168,1,20 72 | #define GATEWAY_IP 192,168,1,1 73 | #define SUBNET_MASK 255,255,255,0 74 | #define BROADCAST_IP 192,168,1,255 75 | // this is the IP address to which output packets are sent, unless multicast is used 76 | #define TARGET_IP 192,168,1,255 77 | 78 | //the Ethernet Shield has an SD card that also communicates by SPI 79 | //set its select pin to output to be safe: 80 | #define SDSELECT_PIN 4 81 | 82 | //*********************** globals *********************** 83 | 84 | //network addresses 85 | byte mac[] = { MAC_ADDRESS }; 86 | IPAddress ip(IP_ADDRESS); 87 | IPAddress gateway(GATEWAY_IP); 88 | IPAddress subnet_mask(SUBNET_MASK); 89 | 90 | #if defined( BROADCAST_IP ) 91 | IPAddress broadcast_ip( BROADCAST_IP); 92 | #else 93 | IPAddress broadcast_ip = INADDR_NONE; 94 | #endif 95 | 96 | // buffer 97 | unsigned char packetBuffer[ARTNET_BUFFER_MAX]; 98 | 99 | // An EthernetUDP instance to let us send and receive packets over UDP 100 | EthernetUDP eUDP; 101 | 102 | // LXDMXEthernet instance ( created in setup so its possible to get IP if DHCP is used ) 103 | LXDMXEthernet* interface; 104 | 105 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 106 | #if defined ( USE_MULTICAST ) 107 | uint8_t use_multicast = USE_SACN; 108 | #else 109 | uint8_t use_multicast = 0; 110 | #endif 111 | 112 | 113 | // Modify trial_address_A and trial_address_B to control other addresses 114 | 115 | uint8_t trial_level = 0; 116 | int trial_address_A = 1; 117 | int trial_address_B = 7; 118 | 119 | IPAddress send_address; 120 | 121 | void setup() { 122 | Serial.begin(115200); 123 | Serial.println("Setup started"); 124 | 125 | #if defined(SDSELECT_PIN) 126 | pinMode(SDSELECT_PIN, OUTPUT); 127 | #endif 128 | 129 | if ( USE_DHCP ) { // Initialize Ethernet 130 | Ethernet.begin(mac); // DHCP 131 | } else { 132 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 133 | } 134 | 135 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 136 | interface = new LXSACN(); 137 | //interface->setUniverse(1); // for different universe, change this line and the multicast address below 138 | if ( use_multicast ) { 139 | send_address = IPAddress(239,255,0,1); 140 | } else { 141 | send_address = IPAddress(TARGET_IP); 142 | } 143 | } else { 144 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask()); 145 | #if defined( BROADCAST_IP ) 146 | send_address = broadcast_ip; 147 | #else 148 | send_address = IPAddress(TARGET_IP); 149 | #endif 150 | 151 | //((LXArtNet*)interface)->setSubnetUniverse(0, 0); //for different subnet/universe, change this line 152 | } 153 | 154 | if ( use_multicast ) { 155 | eUDP.beginMulticast(send_address, interface->dmxPort()); 156 | } else { 157 | eUDP.begin(interface->dmxPort()); 158 | } 159 | 160 | interface->setNumberOfSlots(512); 161 | 162 | Serial.println("Setup complete."); 163 | } 164 | 165 | /************************************************************************ 166 | 167 | The main loop fades the levels of addresses A and B to full 168 | 169 | *************************************************************************/ 170 | 171 | void loop() { 172 | // advance the level (overflows back to zero) 173 | trial_level++; 174 | // set the address levels 175 | interface->setSlot(trial_address_A, trial_level); 176 | interface->setSlot(trial_address_B, trial_level); 177 | // send the network packet 178 | interface->sendDMX(&eUDP, send_address); 179 | delay(25); 180 | Serial.println(trial_level); 181 | } 182 | -------------------------------------------------------------------------------- /examples/TwoUniverses/TwoUniverses.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /*! 3 | @file 2Universes.ino 4 | @author Claude Heintz 5 | @license BSD (see LXDMXEthernet.h) 6 | @copyright 2015-2016 by Claude Heintz All Rights Reserved 7 | 8 | Example using LXDMXEthernet_Library for output of Art-Net or E1.31 sACN from 9 | EEthernet connection to DMX 10 | 11 | Art-Net(TM) Designed by and Copyright Artistic Licence Holdings Ltd. 12 | sACN E 1.31 is a public standard published by the PLASA technical standards program 13 | 14 | Note: 2 Universes may excede the memory available on ATmega328 boards 15 | such as Arduino Uno. 16 | 17 | @section HISTORY 18 | 19 | v1.00 - First release 20 | */ 21 | /**************************************************************************/ 22 | 23 | #include 24 | 25 | /******** Important note about Ethernet library ******** 26 | There are various ethernet shields that use differnt Wiznet chips w5100, w5200, w5500 27 | It is necessary to use an Ethernet library that supports the correct chip for your shield 28 | Perhaps the best library is the one by Paul Stoffregen which supports all three chips: 29 | 30 | https://github.com/PaulStoffregen/Ethernet 31 | 32 | The Paul Stoffregen version is much faster than the built-in Ethernet library and is 33 | neccessary if the shield receives more than a single universe of packets. 34 | 35 | The Ethernet Shield v2 uses a w5500 chip and will not work with the built-in Ethernet library 36 | The library manager does have an Ethernet2 library which supports the w5500. To use this, 37 | uncomment the line to define ETHERNET_SHIELD_V2 38 | */ 39 | #if defined ( ETHERNET_SHIELD_V2 ) 40 | #include 41 | #include 42 | #else 43 | #include 44 | #include 45 | #endif 46 | 47 | #include 48 | #ifdef __AVR__ 49 | #include 50 | #endif 51 | #include 52 | #include 53 | #include 54 | 55 | //*********************** defines *********************** 56 | 57 | #define PIN 6 58 | #define NUM_LEDS 14 59 | 60 | // Make choices here about protocol ( set to 1 to activate option ) 61 | 62 | #define USE_DHCP 0 63 | #define USE_SACN 0 64 | 65 | // dmx protocol interface for parsing packets (created in setup) 66 | LXDMXEthernet* interface; 67 | LXDMXEthernet* interfaceUniverse2; 68 | 69 | // buffer large enough to contain incoming packet 70 | uint8_t packetBuffer[SACN_BUFFER_MAX]; 71 | 72 | //*********************** globals *********************** 73 | 74 | // network addresses 75 | // IP address is defined assuming that the Arduino is connected to 76 | // a Mac without a router and a self assigned ip 77 | byte mac[] = { 0x00, 0x08, 0xDC, 0x4C, 0x29, 0x7E }; //00:08:DC Wiznet 78 | IPAddress ip(169,254,100,100); 79 | IPAddress gateway(169,254,1,1); 80 | IPAddress subnet_mask(255,255,0,0); 81 | 82 | // Adafruit_NeoPixel instance to drive the NeoPixels 83 | Adafruit_NeoPixel ring = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800); 84 | 85 | // An EthernetUDP instance to let us send and receive packets over UDP 86 | EthernetUDP eUDP; 87 | 88 | // sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0) 89 | uint8_t use_multicast = 0; //only single multicast address is supported in this example, will not work for 2 universes 90 | 91 | 92 | //*********************** setup *********************** 93 | 94 | void setup() { 95 | if ( USE_DHCP ) { // Initialize Ethernet 96 | Ethernet.begin(mac); // DHCP 97 | } else { 98 | Ethernet.begin(mac, ip, gateway, gateway, subnet_mask); // Static 99 | } 100 | 101 | if ( USE_SACN ) { // Initialize Interface (defaults to first universe) 102 | interface = new LXSACN(&packetBuffer[0]); 103 | interfaceUniverse2 = new LXSACN(&packetBuffer[0]); 104 | interfaceUniverse2->setUniverse(2); // for different universe, change this line and the multicast address below 105 | } else { 106 | interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask(), &packetBuffer[0]); 107 | interfaceUniverse2 = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask(), &packetBuffer[0]); 108 | ((LXArtNet*)interfaceUniverse2)->setSubnetUniverse(0, 1); //for different subnet/universe, change this line 109 | use_multicast = 0; 110 | } 111 | 112 | if ( use_multicast ) { // Start listening for UDP on port 113 | eUDP.beginMulticast(IPAddress(239,255,0,1), interface->dmxPort()); 114 | } else { 115 | eUDP.begin(interface->dmxPort()); 116 | } 117 | 118 | //announce presence via Art-Net Poll Reply (only advertises one universe) 119 | if ( ! USE_DHCP ) { 120 | ((LXArtNet*)interface)->send_art_poll_reply(&eUDP); 121 | } 122 | pinMode(3, OUTPUT); 123 | pinMode(5, OUTPUT); 124 | 125 | ring.begin(); // Initialize NeoPixel driver 126 | ring.show(); 127 | } 128 | 129 | /************************************************************************ 130 | 131 | The main loop checks for and reads packets from UDP socket 132 | connection. readDMXPacketContents() returns RESULT_DMX_RECEIVED when a DMX packet is received. 133 | If the first universe does not match, try the second 134 | 135 | *************************************************************************/ 136 | 137 | void loop() { 138 | uint16_t packetSize = eUDP.parsePacket(); 139 | if ( packetSize ) { 140 | packetSize = eUDP.read(packetBuffer, SACN_BUFFER_MAX); 141 | uint8_t read_result = interface->readDMXPacketContents(&eUDP, packetSize); 142 | uint8_t read_result2 = 0; 143 | 144 | if ( read_result == RESULT_DMX_RECEIVED ) { 145 | // edge case test universe 1, slot 1 146 | analogWrite(3,interface->getSlot(1)); 147 | ring.setPixelColor(1, interface->getSlot(1), interface->getSlot(2), interface->getSlot(3)); 148 | ring.show(); 149 | } else if ( read_result == RESULT_NONE ) { // if not good dmx first universe (or art poll), try 2nd 150 | read_result2 = interfaceUniverse2->readDMXPacketContents(&eUDP, packetSize); 151 | if ( read_result2 == RESULT_DMX_RECEIVED ) { 152 | // edge case test 2nd universe, slot 512 153 | analogWrite(5,interfaceUniverse2->getSlot(512)); 154 | ring.setPixelColor(8, interface->getSlot(510), interface->getSlot(511), interface->getSlot(512)); 155 | ring.show(); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /extras/doc/LXArtNet_h/Classes/LXArtNet/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXArtNet (LXArtNet.h) 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 81 |
Class:
  LXArtNet
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 | 61 |
62 | 64 |
63 | Member Data
65 |
66 | Protected 67 |
71 |
72 |

Other Reference

73 |
74 | 75 | 76 |
Header
77 |
78 | 79 | 80 |

 

82 |

83 | 84 | -------------------------------------------------------------------------------- /extras/doc/LXArtNet_h/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXArtNet.h 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 34 |
Header:
  LXArtNet.h
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 |
25 | 27 |
26 | Classes
28 |
29 |
31 | 32 | 33 |

 

35 |

36 | 37 | -------------------------------------------------------------------------------- /extras/doc/LXDMXEthernet_h/Classes/LXDMXEthernet/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXDMXEthernet (LXDMXEthernet.h) 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 52 |
Class:
  LXDMXEthernet
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 |
25 | 27 |
26 | Member Functions
28 |
29 | Public 30 |
42 |
43 |

Other Reference

44 |
45 | 46 | 47 |
Header
48 |
49 | 50 | 51 |

 

53 |

54 | 55 | -------------------------------------------------------------------------------- /extras/doc/LXDMXEthernet_h/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXDMXEthernet.h 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 34 |
Header:
  LXDMXEthernet.h
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 |
25 | 27 |
26 | Classes
28 |
29 |
31 | 32 | 33 |

 

35 |

36 | 37 | -------------------------------------------------------------------------------- /extras/doc/LXSACN_h/Classes/LXSACN/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXSACN (LXSACN.h) 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 72 |
Class:
  LXSACN
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 |
25 | 27 |
26 | Member Functions
28 |
29 | 30 | Public 34 |  
48 | Protected 49 |
53 |
54 | 56 |
55 | Member Data
57 |
58 | Protected 59 |
62 |
63 |

Other Reference

64 |
65 | 66 | 67 |
Header
68 |
69 | 70 | 71 |

 

73 |

74 | 75 | -------------------------------------------------------------------------------- /extras/doc/LXSACN_h/toc.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Documentation for LXSACN.h 6 | 7 | 8 | 9 | 10 | 11 | 14 |
 
15 | 16 | 17 | 34 |
Header:
  LXSACN.h
18 | 19 |
20 | 21 | 22 |
Introduction
23 |
24 |
25 | 27 |
26 | Classes
28 |
29 |
31 | 32 | 33 |

 

35 |

36 | 37 | -------------------------------------------------------------------------------- /extras/doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | LXDMXEthernet_library 4 | 5 | 6 | 7 |

LXDMXEthernet_library

8 |

LXDMXEthernet encapsulates functionality for sending and receiving DMX over ethernet.
9 | It is a virtual class with concrete subclasses LXArtNet and LXSACN which specifically
10 | implement the Artistic Licence Art-Net and PLASA sACN 1.31 protocols. 11 |

12 |

Note: LXDMXEthernet_library requires
13 | Ethernet library used with the original Arduino Ethernet Shield
14 | For the Ethernet v2 shield, use LXDMXEthernet2_library along with Ethernet2 Library 15 |

16 |

For multicast, EthernetUDP.h and EthernetUDP.cpp in the Ethernet library
17 | must be modified to add the beginMulticast method.
18 | See the code at the bottom of LXDMXEthernet.h 19 |

20 |

LXArtNet
21 | LXDMXEthernet
22 | LXSACN

23 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For LXDMXEthernet Library 3 | ####################################### 4 | # Class 5 | ####################################### 6 | 7 | LXDMXEthernet KEYWORD1 8 | LXArtNet KEYWORD1 9 | LXSACN KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions 13 | ####################################### 14 | 15 | dmxPort KEYWORD2 16 | universe KEYWORD2 17 | setUniverse KEYWORD2 18 | numberOfSlots KEYWORD2 19 | setNumberOfSlots KEYWORD2 20 | getSlot KEYWORD2 21 | setSlot KEYWORD2 22 | dmxData KEYWORD2 23 | readDMXPacket KEYWORD2 24 | sendDMX KEYWORD2 25 | 26 | setSubnetUniverse KEYWORD2 27 | sendDMX KEYWORD2 28 | send_art_tod KEYWORD2 29 | send_art_rdm KEYWORD2 30 | 31 | replyData KEYWORD2 32 | send_art_poll_reply KEYWORD2 33 | shortName KEYWORD2 34 | longName KEYWORD2 35 | setArtAddressReceivedCallback KEYWORD2 36 | setStatus1Flag KEYWORD2 37 | setStatus2Flag KEYWORD2 38 | 39 | 40 | ArtAddressRecvCallback KEYWORD2 41 | setArtIpProgReceivedCallback KEYWORD2 42 | setArtTodRequestCallback KEYWORD2 43 | setArtRDMCallback KEYWORD2 44 | setArtCommandCallback KEYWORD2 45 | 46 | 47 | ####################################### 48 | # Constants 49 | ####################################### 50 | 51 | DMX_MIN_SLOTS LITERAL1 52 | DMX_MAX_SLOTS LITERAL1 53 | DMX_UNIVERSE_SIZE LITERAL1 54 | 55 | RESULT_NONE LITERAL1 56 | RESULT_DMX_RECEIVED LITERAL1 57 | RESULT_PACKET_COMPLETE LITERAL1 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=LXDMXEthernet Library 2 | version=1.0 3 | author=Claude Heintz 4 | maintainer=Claude Heintz 5 | sentence=Library supports send/receive of Art-Net or sACN e1.31 DMX over ethernet 6 | paragraph=Library supports sending/receiving Art-Net and sACN e1.31 DMX over ethernet. 7 | category=Communication 8 | url=https://github.com/claudeheintz/LXDMXEthernet_library 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/LXArtNet.h: -------------------------------------------------------------------------------- 1 | /* LXArtNet.h 2 | Copyright 2015-2020 by Claude Heintz Design 3 | see LXDMXEthernet.h for license 4 | 5 | Art-Net(TM) Designed by and Copyright Artistic Licence Holdings Ltd. 6 | */ 7 | 8 | #ifndef LXARTNET_H 9 | #define LXARTNET_H 10 | 11 | #include 12 | #include 13 | #include "LXDMXEthernet.h" 14 | 15 | #define ARTNET_PORT 0x1936 16 | #define ARTNET_BUFFER_MAX 530 17 | #define ARTNET_REPLY_SIZE 240 18 | #define ARTNET_POLL_SIZE 14 19 | #define ARTNET_TOD_PKT_SIZE 1228 20 | #define ARTNET_RDM_PKT_SIZE 281 21 | #define ARTNET_ADDRESS_OFFSET 17 22 | 23 | #define ARTNET_ART_POLL 0x2000 24 | #define ARTNET_ART_POLL_REPLY 0x2100 25 | #define ARTNET_ART_CMD 0x2400 26 | #define ARTNET_ART_DMX 0x5000 27 | #define ARTNET_ART_ADDRESS 0x6000 28 | #define ARTNET_ART_IPPROG 0xF800 29 | #define ARTNET_ART_IPPROG_REPLY 0xF900 30 | #define ARTNET_ART_TOD_REQUEST 0x8000 31 | #define ARTNET_ART_TOD_CONTROL 0x8200 32 | #define ARTNET_ART_RDM 0x8300 33 | #define ARTNET_NOP 0x0000 34 | 35 | typedef void (*ArtNetReceiveCallback)(void); 36 | typedef void (*ArtNetDataRecvCallback)(uint8_t* pdata); 37 | 38 | /*! 39 | @class LXArtNet 40 | @abstract 41 | LXArtNet partially implements the Art-Net Ethernet Communication Standard. 42 | 43 | LXArtNet is primarily a node implementation. It supports output of a single universe 44 | of DMX data from the network. It does not support merge and will only accept 45 | packets from the first IP address from which it receives an ArtDMX packet. 46 | This can be reset by sending an ArtAddress cancel merge command. 47 | 48 | When reading packets, LXArtNet will automatically respond to ArtPoll packets. 49 | Depending on the constructor used, it will either broadcast the reply or will 50 | reply directly to the sender of the poll. 51 | 52 | http://www.artisticlicence.com 53 | */ 54 | class LXArtNet : public LXDMXEthernet { 55 | 56 | public: 57 | /*! 58 | * @brief constructor with address used for ArtPollReply 59 | * @param address sent in ArtPollReply 60 | */ 61 | LXArtNet ( IPAddress address ); 62 | /*! 63 | * @brief constructor creates broadcast address for Poll Reply 64 | * @param address sent in ArtPollReply 65 | * @param subnet_mask used to set broadcast address 66 | */ 67 | LXArtNet ( IPAddress address, IPAddress subnet_mask ); 68 | /*! 69 | * @brief constructor creates instance with external buffer for UDP packet 70 | * @param address sent in ArtPollReply 71 | * @param subnet_mask used to set broadcast address 72 | * @param buffer external buffer for UDP packets 73 | */ 74 | LXArtNet ( IPAddress address, IPAddress subnet_mask, uint8_t* buffer ); 75 | 76 | 77 | /*! 78 | * @brief destructor for LXArtNet (frees packet buffer if allocated with constructor) 79 | */ 80 | ~LXArtNet ( void ); 81 | 82 | /*! 83 | * @brief UDP port used by protocol 84 | */ 85 | uint16_t dmxPort ( void ) { return ARTNET_PORT; } 86 | 87 | /*! 88 | * @brief universe for sending and receiving dmx 89 | * @discussion First universe is zero for Art-Net. High nibble is subnet, low nibble is universe. 90 | * @return universe 0-255 91 | */ 92 | uint8_t universe ( void ); 93 | /*! 94 | * @brief set universe for sending and receiving 95 | * @discussion First universe is zero for Art-Net. High nibble is subnet, low nibble is universe. 96 | * @param u universe 0-255 97 | */ 98 | void setUniverse ( uint8_t u ); 99 | /*! 100 | * @brief set subnet/universe for sending and receiving 101 | * @discussion First universe is zero for Art-Net. Sets separate nibbles: high/subnet, low/universe. 102 | * @param s subnet 0-16 103 | * @param u universe 0-16 104 | */ 105 | void setSubnetUniverse ( uint8_t s, uint8_t u ); 106 | /*! 107 | * @brief set universe for sending and receiving 108 | * @discussion First universe is zero for Art-Net. High nibble is subnet, low nibble is universe. 109 | * 0x7f is no change, otherwise if high bit is set, low nibble becomes universe (subnet remains the same) 110 | * @param u universe 0-16 + flag 0x80 111 | */ 112 | void setUniverseAddress ( uint8_t u ); 113 | /*! 114 | * @brief set subnet for sending and receiving 115 | * @discussion First universe is zero for Art-Net. High nibble is subnet, low nibble is universe. 116 | * 0x7f is no change, otherwise if high bit is set, low nibble becomes subnet (universe remains the same) 117 | * @param s subnet 0-16 + flag 0x80 118 | */ 119 | void setSubnetAddress ( uint8_t s ); 120 | /*! 121 | * @brief set net for sending and receiving 122 | * @discussion Net portion of Port-Address, Net+Subnet+Universe (15bits, 7+4+4) 123 | * requires bit 7 to be set to change from ArtAddress packet other 6-0 bits are the net setting 124 | * @param s subnet 0-127 + flag 0x80 125 | */ 126 | void setNetAddress ( uint8_t s ); 127 | 128 | /*! 129 | * @brief set local IPAddress for ArtPollReply 130 | * @discussion should be called if IPAddress changes 131 | */ 132 | void setLocalIP ( IPAddress a ); 133 | void setLocalIP ( IPAddress a, IPAddress sn ); 134 | 135 | /*! 136 | * @brief enables double buffering of received DMX data, merging from two sources 137 | * @discussion enableHTP allocates three 512 byte data buffers A, B, and Merged. 138 | when ArtDMX is received, the data is copied into A or b 139 | based on the IP address of the sender. The highest level 140 | for each slot is written to the merged HTP buffer. 141 | Read the data from the HTP buffer using getHTPSlot(n). 142 | enableHTP() is not available on an ATmega168, ATmega328, or 143 | ATmega328P due to RAM size. 144 | */ 145 | void enableHTP(); 146 | 147 | /*! 148 | * @brief number of slots (aka addresses or channels) 149 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 150 | * @return number of slots/addresses/channels 151 | */ 152 | int numberOfSlots ( void ); 153 | /*! 154 | * @brief set number of slots (aka addresses or channels) 155 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 156 | * @param n number of slots 1 to 512 157 | */ 158 | void setNumberOfSlots ( int n ); 159 | /*! 160 | * @brief get level data from slot/address/channel 161 | * @param slot 1 to 512 162 | * @return level for slot (0-255) 163 | */ 164 | uint8_t getSlot ( int slot ); 165 | /*! 166 | * @brief get level data from slot/address/channel when merge/double buffering is enabled 167 | * @discussion You must call enableHTP() once after the constructor before using getHTPSlot() 168 | * @param slot 1 to 512 169 | * @return level for slot (0-255) 170 | */ 171 | uint8_t getHTPSlot ( int slot ); 172 | /*! 173 | * @brief set level data (0-255) for slot/address/channel 174 | * @param slot 1 to 512 175 | * @param value level 0 to 255 176 | */ 177 | void setSlot ( int slot, uint8_t value ); 178 | 179 | /*! 180 | * @brief direct pointer to dmx buffer uint8_t[] 181 | * @return uint8_t* to dmx data buffer 182 | */ 183 | uint8_t* dmxData ( void ); 184 | /*! 185 | * @brief direct pointer to poll reply packet contents 186 | * @return uint8_t* to poll reply packet contents 187 | */ 188 | uint8_t* replyData ( void ); 189 | 190 | /*! 191 | * @brief direct pointer to part of poll reply packet content array storing the short name 192 | * @return char* to part of poll reply array 193 | */ 194 | char* shortName ( void ); 195 | 196 | /*! 197 | * @brief direct pointer to part of poll reply packet content array storing the long name 198 | * @return char* to part of poll reply array 199 | */ 200 | char* longName ( void ); 201 | 202 | /*! 203 | * @brief copies string into poll reply location storing the long name of the node 204 | * @param char* of string to copy 205 | */ 206 | void setNodeName ( const char* name ); 207 | 208 | /*! 209 | * @brief read UDP packet 210 | * @param eUDP pointer to UDP object to be used for getting UDP packet 211 | * @return 1 if packet contains dmx 212 | */ 213 | uint8_t readDMXPacket ( UDP* eUDP ); 214 | /*! 215 | * @brief read contents of packet from _packet_buffer 216 | * @discussion _packet_buffer should already contain packet payload when this is called 217 | * @param wUDP WiFiUDP 218 | * @param packetSize size of received packet 219 | * @return 1 if packet contains dmx 220 | */ 221 | uint8_t readDMXPacketContents ( UDP* eUDP, int packetSize ); 222 | /*! 223 | * @brief process packet, reading it into _packet_buffer 224 | * @param eUDP UDP* (used for Poll Reply if applicable) 225 | * @return Art-Net opcode of packet 226 | */ 227 | uint16_t readArtNetPacket ( UDP* eUDP ); 228 | /*! 229 | * @brief read contents of packet from _packet_buffer 230 | * @param wUDP WiFiUDP (used for Poll Reply if applicable) 231 | * @param packetSize size of received packet 232 | * @return Art-Net opcode of packet 233 | */ 234 | uint16_t readArtNetPacketContents ( UDP* eUDP, int packetSize ); 235 | 236 | /*! 237 | * @brief read dmx data from ArtDMX packet 238 | * @param wUDP WiFiUDP (used for Poll Reply if applicable) 239 | * @param slots number of slots to read 240 | * @return opcode ( 241 | */ 242 | uint16_t readArtDMX ( UDP* eUDP, uint16_t slots, int packetSize ); 243 | /*! 244 | * @brief send Art-Net ArtDMX packet for dmx output from network 245 | * @param eUDP UDP* to be used for sending UDP packet 246 | * @param to_ip target address 247 | */ 248 | void sendDMX ( UDP* eUDP, IPAddress to_ip ); 249 | 250 | /*! 251 | * @brief send Art-Net ArtPoll to broadcast address 252 | * @param eUDP UDP* to be used for sending UDP packet 253 | * @discussion does nothing if broadcast address is undefined 254 | */ 255 | void send_art_poll( UDP* eUDP ); 256 | /*! 257 | * @brief send ArtPoll Reply packet for dmx output from network 258 | * @discussion If broadcast address is defined by passing subnet to constructor, reply is broadcast 259 | * Otherwise, reply is unicast to remoteIP belonging to the sender of the poll 260 | * @param eUDP pointer to UDP object to be used for sending UDP packet 261 | */ 262 | void send_art_poll_reply ( UDP* eUDP ); 263 | 264 | /*! 265 | * @brief send ArtTOD packet for dmx output from network 266 | * @discussion 267 | * @param wUDP pointer to UDP object to be used for sending UDP packet 268 | * @param todata pointer to TOD array of 6 byte UIDs 269 | * @param ucount number of 6 byte UIDs contained in todata 270 | */ 271 | void send_art_tod ( UDP* wUDP, uint8_t* todata, uint8_t ucount ); 272 | 273 | /*! 274 | * @brief send ArtRDM packet 275 | * @discussion 276 | * @param wUDP pointer to UDP object to be used for sending UDP packet 277 | * @param rdmdata pointer to rdm packet to be sent 278 | * @param toa IPAddress to send UDP ArtRDM packet 279 | */ 280 | void send_art_rdm ( UDP* wUDP, uint8_t* rdmdata, IPAddress toa ); 281 | 282 | /*! 283 | * @brief function callback when ArtTODRequest is received 284 | * @discussion callback pointer is to integer 285 | * to distinguish between ARTNET_ART_TOD_REQUEST *0 286 | * and ARTNET_ART_TOD_CONTROL *1 287 | */ 288 | void setArtTodRequestCallback(ArtNetDataRecvCallback callback); 289 | 290 | /*! 291 | * @brief function callback when ArtRDM is received 292 | * @discussion callback has pointer to RDM payload 293 | */ 294 | void setArtRDMCallback(ArtNetDataRecvCallback callback); 295 | 296 | /*! 297 | * @brief function callback when ArtCommand is received 298 | * @discussion callback has pointer to command string 299 | */ 300 | void setArtCommandCallback(ArtNetDataRecvCallback callback); 301 | 302 | /*! 303 | * @brief function callback when ArtPollReply is received 304 | * @discussion callback has pointer to packet data 305 | */ 306 | void setArtPollReplyCallback(ArtNetDataRecvCallback callback); 307 | 308 | /*! 309 | * @brief setup poll reply buffer to indicate output/input 310 | */ 311 | void setOutputFromNetworkMode ( uint8_t can_output ); 312 | 313 | private: 314 | /*! 315 | * @brief buffer that holds contents of incoming or outgoing packet 316 | * @discussion To minimize memory footprint, the default is no double buffer for dmx data. 317 | * readArtNetPacket fills the buffer with the payload of the incoming packet. 318 | * Previous dmx data is invalidated. 319 | */ 320 | uint8_t* _packet_buffer; 321 | 322 | /// indicates the _packet_buffer was allocated by the constructor and is private. 323 | uint8_t _owns_buffer; 324 | 325 | /// array that holds contents of outgoing ArtPollReply packet 326 | static uint8_t _reply_buffer[ARTNET_REPLY_SIZE]; 327 | 328 | 329 | /// number of slots/address/channels 330 | uint16_t _dmx_slots; 331 | /// high nibble subnet, low nibble universe 332 | uint8_t _universe; 333 | /// 7bit net value of Port-Address, Net + Subnet + Universe 334 | uint8_t _net; 335 | /// sequence number for sending ArtDMX packets 336 | uint8_t _sequence; 337 | 338 | /// address included in poll replies 339 | IPAddress _my_address; 340 | /// if subnet is supplied in constructor, holds address to broadcast poll replies 341 | IPAddress _broadcast_address; 342 | /// first sender of an ArtDMX packet (subsequent senders ignored until cancelMerge) 343 | IPAddress _dmx_sender; 344 | 345 | /*! 346 | * @brief indicates dmx data is double buffered 347 | * @discussion In order to support HTPmerge, 3 buffers to hold DMX data must be allocated 348 | this is done by calling enableHTP() 349 | */ 350 | uint8_t _using_htp; 351 | 352 | /// buffer that holds dmx data received from first source 353 | uint8_t* _dmx_buffer_a; 354 | /// buffer that holds dmx data received from other source 355 | uint8_t* _dmx_buffer_b; 356 | /// composite HTP buffer 357 | uint8_t* _dmx_buffer_c; 358 | /// number of slots/address/channels 359 | uint16_t _dmx_slots_a; 360 | /// number of slots/address/channels 361 | uint16_t _dmx_slots_b; 362 | /// second sender of an ArtDMX packet 363 | IPAddress _dmx_sender_b; 364 | 365 | /*! 366 | * @brief Pointer to art tod request callback 367 | */ 368 | ArtNetDataRecvCallback _art_tod_req_callback; 369 | 370 | /*! 371 | * @brief Pointer to art RDM packet received callback function 372 | */ 373 | ArtNetDataRecvCallback _art_rdm_callback; 374 | 375 | /*! 376 | * @brief Pointer to art Command packet received callback function 377 | */ 378 | ArtNetDataRecvCallback _art_cmd_callback; 379 | 380 | /*! 381 | * @brief Pointer to art poll reply received callback function 382 | */ 383 | ArtNetDataRecvCallback _art_poll_reply_callback; 384 | 385 | /*! 386 | * @brief checks packet for "Art-Net" header 387 | * @return opcode if Art-Net packet 388 | */ 389 | uint16_t parse_header ( void ); 390 | /*! 391 | * @brief utility for parsing ArtAddress packets 392 | * @return opcode in case command changes dmx data 393 | */ 394 | uint16_t parse_art_address ( UDP* wUDP ); 395 | 396 | /*! 397 | * @brief utility for parsing ArtTODRequest packets 398 | */ 399 | uint16_t parse_art_tod_request( UDP* wUDP ); 400 | uint16_t parse_art_tod_control( UDP* wUDP ); 401 | 402 | /*! 403 | * @brief utility for parsing ArtRDM packets 404 | */ 405 | uint16_t parse_art_rdm( UDP* wUDP ); 406 | 407 | /*! 408 | * @brief utility for parsing ArtCommand packets 409 | */ 410 | void parse_art_cmd( UDP* wUDP ); 411 | 412 | /*! 413 | * @brief initialize data structures 414 | */ 415 | void initialize ( uint8_t* b ); 416 | 417 | /*! 418 | * @brief calls art_poll_reply_callback 419 | */ 420 | uint16_t parse_art_poll_reply( UDP* wUDP ); 421 | 422 | /*! 423 | * @brief initialize poll reply buffer 424 | */ 425 | void initializePollReply ( void ); 426 | 427 | }; 428 | 429 | #endif // ifndef LXARTNET_H 430 | -------------------------------------------------------------------------------- /src/LXDMXEthernet.h: -------------------------------------------------------------------------------- 1 | /* LXDMXEthernet.h 2 | Copyright 2015-2016 by Claude Heintz Design 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of LXDMXEthernet nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #include 31 | 32 | #ifndef LXDMXETHERNET_H 33 | #define LXDMXETHERNET_H 34 | 35 | #include 36 | #include 37 | 38 | #define RESULT_NONE 0 39 | #define RESULT_DMX_RECEIVED 1 40 | #define RESULT_PACKET_COMPLETE 2 41 | 42 | #define DMX_UNIVERSE_SIZE 512 43 | 44 | //#define NO_HTP_IS_SINGLE_SENDER 1 45 | 46 | #ifndef INADDR_NONE 47 | #define INADDR_NONE IPAddress(0, 0, 0, 0) 48 | #endif 49 | 50 | /*! 51 | @class LXDMXEthernet 52 | @abstract 53 | LXDMXEthernet encapsulates functionality for sending and receiving DMX over ethernet. 54 | It is a virtual class with concrete subclasses LXArtNet and LXSACN which specifically 55 | implement the Artistic Licence Art-Net and PLASA sACN 1.31 protocols. 56 | 57 | Note: LXDMXEthernet_library requires 58 | Ethernet library used with the original Arduino Ethernet Shield 59 | For the Ethernet v2 shield, use the Ethernet2 Library. 60 | If the sketch compiles and loads correctly but there is no indication of packets 61 | being received, check to make sure the version of the Ethernet library matches 62 | that of your shield. 63 | 64 | For multicast, EthernetUdp.h/EthernetUdp2.cpp in the Ethernet/Ethernet2 library 65 | may need to be modified to add the beginMulticast method. 66 | See the code at the bottom of LXDMXEthernet.h 67 | */ 68 | 69 | class LXDMXEthernet { 70 | 71 | public: 72 | /*! 73 | * @brief UDP port used by protocol 74 | */ 75 | virtual uint16_t dmxPort ( void ); 76 | 77 | /*! 78 | * @brief universe for sending and receiving dmx 79 | * @discussion First universe is zero for Art-Net and one for sACN E1.31. 80 | * @return universe 0/1-255 81 | */ 82 | virtual uint8_t universe ( void ); 83 | /*! 84 | * @brief set universe for sending and receiving 85 | * @discussion First universe is zero for Art-Net and one for sACN E1.31. 86 | * @param u universe 0/1-255 87 | */ 88 | virtual void setUniverse ( uint8_t u ); 89 | /*! 90 | * @brief enables double buffering of received DMX data, merging from two sources 91 | * @discussion enableHTP allocates three 512 byte data buffers A, B, and Merged. 92 | when a DMX packet is received, the data is copied into A or b 93 | based on the IP address of the sender. The highest level 94 | for each slot is written to the merged HTP buffer. 95 | Read the data from the HTP buffer using getHTPSlot(n). 96 | enableHTP() is not available on an ATmega168, ATmega328, or 97 | ATmega328P due to RAM size. 98 | */ 99 | virtual void enableHTP ( void ); 100 | 101 | /*! 102 | * @brief number of slots (aka addresses or channels) 103 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 104 | * @return number of slots/addresses/channels 105 | */ 106 | virtual int numberOfSlots ( void ); 107 | /*! 108 | * @brief set number of slots (aka addresses or channels) 109 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 110 | * @param slots 1 to 512 111 | */ 112 | virtual void setNumberOfSlots ( int n ); 113 | /*! 114 | * @brief get level data from slot/address/channel 115 | * @param slot 1 to 512 116 | * @return level for slot (0-255) 117 | */ 118 | virtual uint8_t getSlot ( int slot ); 119 | /*! 120 | * @brief get level data from slot/address/channel when merge/double buffering is enabled 121 | * @discussion You must call enableHTP() once after the constructor before using getHTPSlot() 122 | * @param slot 1 to 512 123 | * @return level for slot (0-255) 124 | */ 125 | virtual uint8_t getHTPSlot ( int slot ); 126 | /*! 127 | * @brief set level data (0-255) for slot/address/channel 128 | * @param slot 1 to 512 129 | * @param level 0 to 255 130 | */ 131 | virtual void setSlot ( int slot, uint8_t value ); 132 | /*! 133 | * @brief direct pointer to dmx buffer uint8_t[] 134 | * @return uint8_t* to dmx data buffer 135 | */ 136 | virtual uint8_t* dmxData ( void ); 137 | 138 | /*! 139 | * @brief read UDP packet 140 | * @return 1 if packet contains dmx 141 | */ 142 | virtual uint8_t readDMXPacket ( UDP* eUDP ); 143 | 144 | /*! 145 | * @brief read contents of packet from _packet_buffer 146 | * @discussion _packet_buffer should already contain packet payload when this is called 147 | * @param eUDP pointer to UDP object 148 | * @param packetSize size of received packet 149 | * @return 1 if packet contains dmx 150 | */ 151 | virtual uint8_t readDMXPacketContents (UDP* eUDP, int packetSize ); 152 | 153 | /*! 154 | * @brief send the contents of the _packet_buffer to the address to_ip 155 | */ 156 | virtual void sendDMX ( UDP* eUDP, IPAddress to_ip ); 157 | }; 158 | 159 | #endif // ifndef LXDMXETHERNET_H 160 | 161 | 162 | /* 163 | @code 164 | ****************** multicast methods added to Ethernet2 library ******************* 165 | 166 | see https://github.com/aallan/Arduino/blob/3811729f82ef05f3ae43341022e7b65a92d333a2/libraries/Ethernet/EthernetUdp.cpp 167 | 168 | 1) Locate the Ethernet (Etrhernet2) library in the IDE 169 | If you have installed the library, it will be located with other libraries in the /libraries folder. 170 | If it is included in the IDE, you will need to search within the IDE files to find the libraries folder. 171 | On MacOS this is located at Arduino.app/Contents/Java/libraries. 172 | 173 | 2) If modifying the built-in library, duplicate the Ethernet library folder in your sketchbook folder 174 | (~/Documents/Arduino/libraries) This will override the built-in library. If you modify 175 | a built-in library directly, your changes will be lost if you later upgrade the IDE. 176 | 177 | 3) 178 | Add the required method as follows: 179 | 180 | 3A) In EthernetUdph add the beginMulticast method declaration: 181 | ... 182 | EthernetUdp(); // Constructor 183 | virtual uint8_t begin(uint16_t); // initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use 184 | uint8_t beginMulticast(IPAddress ip, uint16_t port); //############## added to standard library 185 | ... 186 | 187 | 3B) In EthernetUdp.cpp add the method body: 188 | 189 | uint8_t EthernetUdp::beginMulticast(IPAddress ip, uint16_t port) { 190 | if (_sock != MAX_SOCK_NUM) 191 | return 0; 192 | 193 | for (int i = 0; i < MAX_SOCK_NUM; i++) { 194 | uint8_t s = w5500.readSnSR(i); 195 | if (s == SnSR::CLOSED || s == SnSR::FIN_WAIT) { 196 | _sock = i; 197 | break; 198 | } 199 | } 200 | 201 | if (_sock == MAX_SOCK_NUM) 202 | return 0; 203 | 204 | 205 | // Calculate MAC address from Multicast IP Address 206 | byte mac[] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0x00 }; 207 | 208 | mac[3] = ip[1] & 0x7F; 209 | mac[4] = ip[2]; 210 | mac[5] = ip[3]; 211 | 212 | w5500.writeSnDIPR(_sock, rawIPAddress(ip)); //239.255.0.1 213 | w5500.writeSnDPORT(_sock, port); 214 | w5500.writeSnDHAR(_sock,mac); 215 | 216 | _remaining = 0; 217 | 218 | socket(_sock, SnMR::UDP, port, SnMR::MULTI); 219 | 220 | return 1; 221 | } 222 | 223 | ************************************************************************************** 224 | */ 225 | -------------------------------------------------------------------------------- /src/LXSACN.cpp: -------------------------------------------------------------------------------- 1 | /* LXSACN.cpp 2 | Copyright 2015-2015 by Claude Heintz Design 3 | see LXDMXEthernet.h for license 4 | 5 | LXSACN partially implements E1.31, 6 | Lightweight streaming protocol for transport of DMX512 using ACN 7 | 8 | sACN E 1.31 is a public standard published by the PLASA technical standards program 9 | http://tsp.plasa.org/tsp/documents/published_docs.php 10 | */ 11 | 12 | #include "LXSACN.h" 13 | 14 | LXSACN::LXSACN ( void ) 15 | { 16 | initialize(0); 17 | } 18 | 19 | LXSACN::LXSACN ( uint8_t* buffer ) 20 | { 21 | initialize(buffer); 22 | } 23 | 24 | LXSACN::~LXSACN ( void ) 25 | { 26 | if ( _owns_buffer ) { // if we created this buffer, then free the memory 27 | free(_packet_buffer); 28 | } 29 | if ( _using_htp ) { 30 | free(_dmx_buffer_a); 31 | free(_dmx_buffer_b); 32 | free(_dmx_buffer_c); 33 | } 34 | } 35 | 36 | void LXSACN::initialize ( uint8_t* b ) { 37 | if ( b == 0 ) { 38 | // create buffer 39 | _packet_buffer = (uint8_t*) malloc(SACN_BUFFER_MAX); 40 | _owns_buffer = 1; 41 | } else { 42 | // external buffer. Size MUST be >=SACN_BUFFER_MAX 43 | _packet_buffer = b; 44 | _owns_buffer = 0; 45 | } 46 | 47 | //zero buffer and CID 48 | memset(_packet_buffer, 0, SACN_BUFFER_MAX); 49 | memset(_dmx_sender_id, 0, SACN_CID_LENGTH); 50 | memset(_dmx_sender_id_b, 0, SACN_CID_LENGTH); 51 | 52 | _using_htp = 0; 53 | _dmx_buffer_a = 0; 54 | _dmx_buffer_b = 0; 55 | _dmx_buffer_c = 0; 56 | 57 | _dmx_slots = 0; 58 | _dmx_slots_a = 0; 59 | _dmx_slots_b = 0; 60 | _universe = 1; // NOTE: unlike Art-Net, sACN universes begin at 1 61 | _sequence = 1; 62 | } 63 | 64 | uint8_t LXSACN::universe ( void ) { 65 | return _universe; 66 | } 67 | 68 | void LXSACN::setUniverse ( uint8_t u ) { 69 | _universe = u; 70 | } 71 | 72 | void LXSACN::enableHTP() { 73 | #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) 74 | // not enough memory on these to allocate these buffers 75 | #else 76 | if ( ! _using_htp ) { 77 | _dmx_buffer_a = (uint8_t*) malloc(DMX_UNIVERSE_SIZE); 78 | _dmx_buffer_b = (uint8_t*) malloc(DMX_UNIVERSE_SIZE); 79 | _dmx_buffer_c = (uint8_t*) malloc(DMX_UNIVERSE_SIZE); 80 | for(int i=0; i 0 ) { 134 | if ( parse_root_layer(packetSize) ) { 135 | if ( startCode() == 0 ) { 136 | return RESULT_DMX_RECEIVED; 137 | } 138 | } 139 | } 140 | return RESULT_NONE; 141 | } 142 | 143 | uint16_t LXSACN::readSACNPacket ( UDP* eUDP ) { 144 | uint16_t packetSize = eUDP->parsePacket(); 145 | if ( packetSize ) { 146 | packetSize = eUDP->read(_packet_buffer, SACN_BUFFER_MAX); 147 | return parse_root_layer(packetSize); 148 | } 149 | return 0; 150 | } 151 | 152 | void LXSACN::sendDMX( UDP* eUDP, IPAddress to_ip ) { 153 | for (int n=0; n<126; n++) { 154 | _packet_buffer[n] = 0; // zero outside layers & start code 155 | } 156 | //ACN root layer 157 | _packet_buffer[0] = 0; 158 | _packet_buffer[1] = 0x10; 159 | strcpy((char*)&_packet_buffer[4], "ASC-E1.17"); 160 | uint16_t fplusl = _dmx_slots + 110 + 0x7000; 161 | _packet_buffer[16] = fplusl >> 8; 162 | _packet_buffer[17] = fplusl & 0xff; 163 | _packet_buffer[21] = 0x04; 164 | 165 | //CID (UUID) 166 | //fd32aedc-7b94-11e7-bb31-be2e44b06b34 167 | _packet_buffer[22] = 0xfd; 168 | _packet_buffer[23] = 0x32; 169 | _packet_buffer[24] = 0xae; 170 | _packet_buffer[25] = 0xdc; 171 | _packet_buffer[26] = 0x7b; 172 | _packet_buffer[27] = 0x94; 173 | _packet_buffer[28] = 0x11; 174 | _packet_buffer[29] = 0xe7; 175 | _packet_buffer[30] = 0xbb; 176 | _packet_buffer[31] = 0x31; 177 | _packet_buffer[32] = 0xbe; 178 | _packet_buffer[33] = 0x2e; 179 | _packet_buffer[34] = 0x44; 180 | _packet_buffer[35] = 0xb0; 181 | _packet_buffer[36] = 0x6b; 182 | _packet_buffer[37] = 0x34; 183 | 184 | //ACN framing layer 185 | fplusl = _dmx_slots + 88 + 0x7000; 186 | _packet_buffer[38] = fplusl >> 8; 187 | _packet_buffer[39] = fplusl & 0xff; 188 | _packet_buffer[43] = 0x02; 189 | strcpy((char*)&_packet_buffer[44], "Arduino"); 190 | _packet_buffer[108] = 100; //priority 191 | if ( _sequence == 0 ) { 192 | _sequence = 1; 193 | } else { 194 | _sequence++; 195 | } 196 | _packet_buffer[111] = _sequence; 197 | _packet_buffer[113] = _universe >> 8; 198 | _packet_buffer[114] = _universe & 0xff; 199 | 200 | //ACN DMP layer 201 | fplusl = _dmx_slots + 11 + 0x7000; 202 | _packet_buffer[115] = fplusl >> 8; 203 | _packet_buffer[116] = fplusl & 0xff; 204 | _packet_buffer[117] = 0x02; 205 | _packet_buffer[118] = 0xa1; 206 | _packet_buffer[122] = 0x01; 207 | fplusl = _dmx_slots + 1; //plus 1 byte for start code 208 | _packet_buffer[123] = fplusl >> 8; 209 | _packet_buffer[124] = fplusl & 0xFF; 210 | 211 | //assume dmx data has been set 212 | // _packet_buffer[125] is start code (0) 213 | eUDP->beginPacket(to_ip, SACN_PORT); 214 | eUDP->write(_packet_buffer, _dmx_slots + 126); 215 | eUDP->endPacket(); 216 | } 217 | 218 | uint16_t LXSACN::parse_root_layer( int size ) { 219 | if ( ! _using_htp ) { 220 | _dmx_slots = 0; //read into packet buffer which doubles as DMX now invalid until confirmed 221 | } 222 | if ( _packet_buffer[1] == 0x10 ) { //preamble size 223 | if ( strcmp((const char*)&_packet_buffer[4], "ASC-E1.17") == 0 ) { 224 | uint16_t tsize = size - 16; 225 | if ( checkFlagsAndLength(&_packet_buffer[16], tsize) ) { // root pdu length 226 | if ( _packet_buffer[21] == 0x04 ) { // vector RLP is 1.31 data 227 | return parse_framing_layer( tsize ); 228 | } 229 | } 230 | } // ACN packet identifier 231 | } // preamble size 232 | return 0; 233 | } 234 | 235 | uint16_t LXSACN::parse_framing_layer( uint16_t size ) { 236 | 237 | uint16_t tsize = size - 22; 238 | if ( checkFlagsAndLength(&_packet_buffer[38], tsize) ) { // framing pdu length 239 | if ( _packet_buffer[43] == 0x02 ) { // vector dmp is 1.31 240 | if (( _packet_buffer[112] == 0) && ( _packet_buffer[114] == _universe )) { // implementation has 255 universe limit 241 | return parse_dmp_layer( tsize ); 242 | } // [112] options flags non-zero if preview or universe terminated 243 | } 244 | } 245 | return 0; 246 | } 247 | 248 | uint16_t LXSACN::parse_dmp_layer( uint16_t size ) { 249 | uint16_t tsize = size - 77; 250 | if ( checkFlagsAndLength(&_packet_buffer[115], tsize) ) { // dmp pdu length 251 | if ( _packet_buffer[117] == 0x02 ) { // Set Property 252 | if ( _packet_buffer[118] == 0xa1 ) { // address and data format 253 | 254 | if ( _using_htp ) { 255 | uint16_t slots = _packet_buffer[124]; // if same sender, good dmx! 256 | slots += _packet_buffer[123] << 8; 257 | 258 | 259 | // new October 2017 replace sender a if this packet has higher priority 260 | // sender b only maintained for HTP when priority is equal 261 | 262 | uint8_t new_higher_priority = 0; 263 | uint8_t erase_b = 0; 264 | 265 | if ( _packet_buffer[SACN_PRIORITY_OFFSET] > _priority_a ) { 266 | // packet has higher priority than a or b (b only exists if equal to a) 267 | new_higher_priority = 1; 268 | erase_b = 1; 269 | 270 | } else { // has lower or equal priority to sender a priority 271 | 272 | if ( _priority_a > _packet_buffer[SACN_PRIORITY_OFFSET] ) { 273 | // lower priority packet, no need for b if this came from b 274 | if ( checkCID(_dmx_sender_id_b) ) { 275 | erase_b = 1; 276 | } 277 | } 278 | 279 | // but if haven't heard from source a for three seconds... 280 | if ( abs(millis()-_last_packet_a) > 3000 ) { 281 | // no more source a 282 | if ( _packet_buffer[SACN_PRIORITY_OFFSET] > _priority_b ) { 283 | // this packet takes over as source a if it has a higher priority than b 284 | // (priority_b is zero if it does not exist) 285 | // (could even be that this is a tardy packet from a) 286 | new_higher_priority = 1; 287 | erase_b = 1; 288 | } else { 289 | // otherwise copy b => a, handle this packet below as if it is a new b 290 | for(int k=0; k 3000 ) { 327 | clearDMXSourceB(); 328 | } 329 | } 330 | 331 | int di; 332 | int dt = 125 + 1; 333 | if ( _priority_a == _priority_b ) { 334 | if ( _dmx_slots_a > _dmx_slots_b ) { 335 | _dmx_slots = _dmx_slots_a; 336 | } else { 337 | _dmx_slots = _dmx_slots_b; 338 | } 339 | for (di=0; di<_dmx_slots; di++) { 340 | _dmx_buffer_a[di] = _packet_buffer[dt+di]; 341 | if ( _dmx_buffer_a[di] > _dmx_buffer_b[di] ) { 342 | _dmx_buffer_c[di] = _dmx_buffer_a[di]; 343 | } else { 344 | _dmx_buffer_c[di] = _dmx_buffer_b[di]; 345 | } 346 | } //for 347 | } else { 348 | _dmx_slots = _dmx_slots_a; 349 | for (di=0; di<_dmx_slots; di++) { 350 | _dmx_buffer_a[di] = _packet_buffer[dt+di]; 351 | _dmx_buffer_c[di] = _dmx_buffer_a[di]; 352 | } 353 | } 354 | return 1; 355 | } else if ( _packet_buffer[SACN_PRIORITY_OFFSET] == _priority_a ) { 356 | // if CID did not match sender_a and message has equal priority, this could be sender_b 357 | copyCIDifEmpty(_dmx_sender_id_b); 358 | if ( checkCID(_dmx_sender_id_b) ) { 359 | _dmx_slots_b = slots - 1; 360 | _last_packet_b = millis(); 361 | _priority_b = _packet_buffer[SACN_PRIORITY_OFFSET]; 362 | 363 | if ( _dmx_slots_a > _dmx_slots_b ) { 364 | _dmx_slots = _dmx_slots_a; 365 | } else { 366 | _dmx_slots = _dmx_slots_b; 367 | } 368 | 369 | int di; 370 | int dc = _dmx_slots; 371 | int dt = 125 + 1; 372 | for (di=0; di _dmx_buffer_b[di] ) { 375 | _dmx_buffer_c[di] = _dmx_buffer_a[di]; 376 | } else { 377 | _dmx_buffer_c[di] = _dmx_buffer_b[di]; 378 | } 379 | } //for 380 | return 1; 381 | } //matched second CID 382 | } //not first CID and equal priority 383 | } else { //not _using_htp 384 | 385 | #if defined ( NO_HTP_IS_SINGLE_SENDER ) 386 | #warning NO_HTP_IS_SINGLE_SENDER NO_HTP_IS_SINGLE_SENDER NO_HTP_IS_SINGLE_SENDER NO_HTP_IS_SINGLE_SENDER 387 | copyCIDifEmpty(_dmx_sender_id); 388 | if ( checkCID(_dmx_sender_id) ) { 389 | #endif 390 | uint16_t slots = _packet_buffer[124]; // if same sender, good dmx! 391 | slots += _packet_buffer[123] << 8; 392 | _dmx_slots = slots - 1; 393 | return 1; 394 | #if defined ( NO_HTP_IS_SINGLE_SENDER ) 395 | } 396 | #endif 397 | } 398 | } 399 | } 400 | } 401 | return 0; 402 | } 403 | 404 | // utility for checking 2 byte: flags (high nibble == 0x7) && 12 bit length 405 | 406 | uint8_t LXSACN::checkFlagsAndLength( uint8_t* flb, uint16_t size ) { 407 | if ( ( flb[0] & 0xF0 ) == 0x70 ) { 408 | uint16_t pdu_length = flb[1]; 409 | pdu_length += ((flb[0] & 0x0f) << 8); 410 | if ( ( pdu_length != 0 ) && ( size >= pdu_length ) ) { 411 | return 1; 412 | } 413 | } 414 | return 0; 415 | } 416 | 417 | 418 | void LXSACN::clearDMXOutput ( void ) { 419 | for (int n=0; n 14 | #include 15 | #include "LXDMXEthernet.h" 16 | 17 | #define SACN_PORT 0x15C0 18 | #define SACN_BUFFER_MAX 638 19 | #define SACN_PRIORITY_OFFSET 108 20 | #define SACN_ADDRESS_OFFSET 125 21 | #define SACN_CID_LENGTH 16 22 | #define SLOTS_AND_START_CODE 513 23 | 24 | /*! 25 | * @class LXSACN 26 | * @abstract 27 | LXSACN partially implements E1.31, lightweight streaming protocol for transport of DMX512 using ACN 28 | http://tsp.plasa.org/tsp/documents/published_docs.php 29 | 30 | LXSACN is primarily a node implementation. It supports output of a single universe 31 | of DMX data from the network. It does not support merge and will only accept 32 | packets from the first source from which it receives an E1.31 DMX packet. 33 | */ 34 | class LXSACN : public LXDMXEthernet { 35 | 36 | public: 37 | /*! 38 | * @brief constructor for LXSACN 39 | */ 40 | LXSACN ( void ); 41 | /*! 42 | * @brief constructor for LXSACN with external buffer fro UDP packets 43 | * @param external packet buffer 44 | */ 45 | LXSACN ( uint8_t* buffer ); 46 | /*! 47 | * @brief destructor for LXSACN (frees packet buffer if allocated with constructor) 48 | */ 49 | ~LXSACN ( void ); 50 | 51 | /*! 52 | * @brief UDP port used by protocol 53 | */ 54 | uint16_t dmxPort ( void ) { return SACN_PORT; } 55 | 56 | /*! 57 | * @brief universe for sending and receiving dmx 58 | * @discussion First universe is one for sACN E1.31. Range is 1-255 in this implementation, 1-32767 in full E1.31. 59 | * @return universe 1-255 60 | */ 61 | uint8_t universe ( void ); 62 | /*! 63 | * @brief set universe for sending and receiving 64 | * @discussion First universe one for sACN E1.31. Range is 1-255 in this implementation, 1-32767 in full E1.31. 65 | * @param u universe 1-255 66 | */ 67 | void setUniverse ( uint8_t u ); 68 | /*! 69 | * @brief enables double buffering of received DMX data, merging from two sources 70 | * @discussion enableHTP allocates three 512 byte data buffers A, B, and Merged. 71 | when sACN DMX is received, the data is copied into A or b 72 | based on the IP address of the sender. The highest level 73 | for each slot is written to the merged HTP buffer. 74 | Read the data from the HTP buffer using getHTPSlot(n). 75 | enableHTP() is not available on an ATmega168, ATmega328, or 76 | ATmega328P due to RAM size. 77 | */ 78 | void enableHTP(); 79 | 80 | /* 81 | * @brief number of slots (aka addresses or channels) 82 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 83 | * @return number of slots/addresses/channels 84 | */ 85 | int numberOfSlots ( void ); 86 | /*! 87 | * @brief set number of slots (aka addresses or channels) 88 | * @discussion Should be minimum of ~24 depending on actual output speed. Max of 512. 89 | * @param n number of slots 1 to 512 90 | */ 91 | void setNumberOfSlots ( int n ); 92 | /*! 93 | * @brief get level data from slot/address/channel 94 | * @param slot 1 to 512 95 | * @return level for slot (0-255) 96 | */ 97 | uint8_t getSlot ( int slot ); 98 | /*! 99 | * @brief get level data from slot/address/channel when merge/double buffering is enabled 100 | * @discussion You must call enableHTP() once after the constructor before using getHTPSlot() 101 | * @param slot 1 to 512 102 | * @return level for slot (0-255) 103 | */ 104 | uint8_t getHTPSlot ( int slot ); 105 | /*! 106 | * @brief set level data (0-255) for slot/address/channel 107 | * @param slot 1 to 512 108 | * @param level 0 to 255 109 | */ 110 | void setSlot ( int slot, uint8_t value ); 111 | /*! 112 | * @brief dmx start code (set to zero for standard dmx) 113 | * @return dmx start code 114 | */ 115 | uint8_t startCode ( void ); 116 | /*! 117 | * @brief sets dmx start code for outgoing packets 118 | * @param value start code (set to zero for standard dmx) 119 | */ 120 | void setStartCode ( uint8_t value ); 121 | /*! 122 | * @brief direct pointer to dmx buffer uint8_t[] 123 | * @return uint8_t* to dmx data buffer 124 | */ 125 | uint8_t* dmxData ( void ); 126 | 127 | /*! 128 | * @brief read UDP packet 129 | * @param eUDP UDP* object to be used for getting UDP packet 130 | * @return 1 if packet contains dmx 131 | */ 132 | uint8_t readDMXPacket ( UDP* eUDP ); 133 | 134 | /*! 135 | * @brief read contents of packet from _packet_buffer 136 | * @discussion _packet_buffer should already contain packet payload when this is called 137 | * @param eUDP UDP* 138 | * @param packetSize size of received packet 139 | * @return 1 if packet contains dmx 140 | */ 141 | virtual uint8_t readDMXPacketContents (UDP* eUDP, int packetSize ); 142 | 143 | /*! 144 | * @brief process packet, reading it into _packet_buffer 145 | * @param eUDP UDP* 146 | * @return number of dmx slots read or 0 if not dmx/invalid 147 | */ 148 | uint16_t readSACNPacket ( UDP* eUDP ); 149 | /*! 150 | * @brief send sACN E1.31 packet for dmx output from network 151 | * @param eUDP UDP* object to be used for sending UDP packet 152 | * @param to_ip target address 153 | */ 154 | void sendDMX ( UDP* eUDP, IPAddress to_ip ); 155 | 156 | 157 | void clearDMXOutput ( void ); 158 | 159 | private: 160 | /*! 161 | * @brief buffer that holds contents of incoming or outgoing packet 162 | * @discussion There is no double buffer for dmx data. 163 | * readSACNPacket fills the buffer with the payload of the incoming packet. 164 | * Previous dmx data is invalidated. 165 | */ 166 | uint8_t* _packet_buffer; 167 | /*! 168 | * @brief indicates was created by constructor 169 | */ 170 | uint8_t _owns_buffer; 171 | /// number of slots/address/channels 172 | int _dmx_slots; 173 | /// universe 1-255 in this implementation 174 | uint16_t _universe; 175 | /// sequence number for sending sACN DMX packets 176 | uint8_t _sequence; 177 | /// cid of first sender of an E 1.31 DMX packet (subsequent senders ignored unless using htp) 178 | uint8_t _dmx_sender_id[16]; 179 | 180 | /*! 181 | * @brief indicates dmx data is double buffered 182 | * @discussion In order to support HTPmerge, 3 buffers to hold DMX data must be allocated 183 | this is done by calling enableHTP() 184 | */ 185 | uint8_t _using_htp; 186 | 187 | /// buffer that holds dmx data received from first source 188 | uint8_t* _dmx_buffer_a; 189 | /// buffer that holds dmx data received from other source 190 | uint8_t* _dmx_buffer_b; 191 | /// composite HTP buffer 192 | uint8_t* _dmx_buffer_c; 193 | /// number of slots/address/channels 194 | uint16_t _dmx_slots_a; 195 | /// number of slots/address/channels 196 | uint16_t _dmx_slots_b; 197 | /// cid of second sender of an E 1.31 DMX packet 198 | uint8_t _dmx_sender_id_b[16]; 199 | /// priority of primary sender (higher than or equal to secondary) 200 | uint8_t _priority_a; 201 | /// priority of second sender (only equal to primary, less is ignored, higher becomes primary) 202 | uint8_t _priority_b; 203 | /// timestamp of last packet received from primary sender 204 | long _last_packet_a; 205 | /// timestamp of last packet received from second sender 206 | long _last_packet_b; 207 | 208 | /*! 209 | * @brief checks the buffer for the sACN header and root layer size 210 | */ 211 | uint16_t parse_root_layer ( int size ); 212 | /*! 213 | * @brief checks the buffer for the sACN header and root layer size 214 | */ 215 | uint16_t parse_framing_layer ( uint16_t size ); 216 | /*! 217 | * @brief dmp layer is where DMX data is located 218 | */ 219 | uint16_t parse_dmp_layer ( uint16_t size ); 220 | /*! 221 | * @brief utility for checking integrity of ACN packet 222 | */ 223 | uint8_t checkFlagsAndLength ( uint8_t* flb, uint16_t size ); 224 | /*! 225 | * @brief utility for matching CID contained in packet 226 | */ 227 | uint8_t checkCID ( uint8_t* cid ); 228 | /*! 229 | * @brief copies CID from packet to array (if empty) 230 | */ 231 | void copyCIDifEmpty ( uint8_t* cid ); 232 | 233 | /*! 234 | * @brief initialize data structures 235 | */ 236 | void initialize ( uint8_t* b ); 237 | 238 | /*! 239 | * @brief clear "b" dmx buffer and sender CID 240 | */ 241 | void clearDMXSourceB( void ); 242 | 243 | }; 244 | 245 | #endif // ifndef LXSACNDMX_H 246 | --------------------------------------------------------------------------------