adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.07k stars 1.2k forks source link

DMX (lighting protocol) #673

Open tannewt opened 6 years ago

tannewt commented 6 years ago

This is used by DJs to control lighting.

siddacious commented 6 years ago

If this ever gets done a great project would be an Artemis "red alert" siren:

http://artemiswiki.pbworks.com/w/page/68387358/DMX%20Interface

siddacious commented 6 years ago

I found these which might be useful reference https://github.com/jaydcarlson/microcontroller-test-code/tree/master/SAMD10/dmx https://github.com/claudeheintz/LXSAMD21DMX

s-light commented 5 years ago

it is also used in theater, music-shows and other stage / event things . also in architecture (storefront and similar ambient-lighting)

some more references i found on my search that could be helpfull:

ariherzig commented 4 years ago

I'm also very interested in there being a way to send and receive DMX-512 data with CircuitPython.

tannewt commented 4 years ago

Can this be done with UART at the lowest level? Do we just need a Python library for it?

What is the least expensive testing setup?

wallarug commented 4 years ago

Hey @tannewt ,

A Python library would be the easiest way to do it. But it might need doing at the hardware level of the SAMD21/51. I read in the datasheet that these chips should support RS485 at the hardware level.

I know only one python library that does this well at the moment. https://github.com/mathertel/DMXSerial

At the heart of DMX:

Element 14 DMX Explained

wallarug commented 4 years ago

After extensive reading from the adafruit forums from 2018 ... please ignore my comments about direct hardware support.

The SAMD51 has some support for RS485 but external hardware is still required. The SAMD21 has no built in support.

The only way is to use a transceiver module (MAX3485 is most popular) and normal UART protocols with a packet decoding layer (presumably this is what everyone in this issue is looking for).

I'd imagine anyone who wants this would do something similar to the below:

class DMX512:

    def __init__(self, uart):
        self._device = uart

    def read(self):
        # recieve 513 packets from DMX protocol
        data = self._device.read(513)

        # do something with the data
        #  maybe remove first packet

        recv = data[1:]

        return recv

    def write(self, values):
        # send 512 bytes via DMX protocol
        # check data first
        if len(values) < 512:
            raise Exception("data too short")

        # maybe convert values to bytes

        # send data to devices
        self._device.write(values)

I have been looking into this for about 7 years and been doing stage lighting for 10+ years - that's why I'm interested 😄 .

s-light commented 4 years ago

@tannewt least expensive could be some 'amazon dmx led spot' (you never know if they are conform to the specifications...) - the better setup: a scope or digital-logger/analyzer thing... ;-) or just some already 'proven to work' arduino library.. all the implementation can be tested in the 3.3V / 5V TTL levels... as @wallarug has written - to get to the actuall DMX512 HW-Layer ('just' RS485) some differential driver /receiver is needed.. here would be the challenge to check and find one that is 3.3V compatible.. out of my head i don't know one.. but there are lots of options form different manufactures - example TI - and than its just a question of 'do you want to be specification conformant' -
if true you have to think about isolation...
if not - just ignore isolation - in most small setups it works ;-)

theoretically you could do it with just an UART implementation.. but this could get hacky - as you need to fastly switch baudrates to generate the stop bit timming.. (if i remember correctly) lower level - maybe the 'USART' support in the chip can handle this thing already in a smoother way... DMX is in the basic version only Master → Receiver → Receiver→ Receiver. so uni-directional.

if someone wants to implement RDM (remote device managment) it gets more complicated and timing-critical - as now the master has to listen between sending normal frames for the responses from the devices. so its half-duplex - for this you normally need to toggle a pin to tell the driver if it is in send or receive mode..

@wallarug your example is only partly correct: DMX is not specified to transmit all 512 channels. you are allowed to send less! and sometimes that is usefull! in my classic arduino projects i used this to reduce performance /memory problems ;-) (only sending the ~12 channels the device needed...)

tannewt commented 4 years ago

Thanks for the info. To summarize: I don't think there is any core work needed for this. On the drivers side we'd likely need a community library for the external transmitter IC and then a library for the DMX protocol on top of that.

I don't have any experience with DMX so someone else will be a better person to do it. I'm happy to consult on the CircuitPython side of things but won't be picking this up directly myself.

s-light commented 4 years ago

the external IC is only 'levelshifting' so no lib there.. maybe we need some special edge cases implemented in the UART Core... in total you are right - best is if someone with deep detail understanding of the DMX-Protocoll finds the time to implement it -and then we will know where the limits are.. i think i could do it - but i don't know if and when i find spare time for it - so if there is someone else willing i could support/help with some protocoll information and testing with some hardware-setups.

Tasm-Devil commented 4 years ago

U can start with this fragment:

import board
import busio
import digitalio
import time
from array import array
import gc

class DMX():
    def __init__(self, max_channels):
        # First byte is always 0, 512 after that is the 512 channels
        self.dmx_message = array('B', [0] * (max_channels+1))
        self.dmx_uart = digitalio.DigitalInOut(board.TX)
        self.dmx_uart.direction = digitalio.Direction.OUTPUT
        self.dmx_uart.value = 1

    def set_channels(self, message):
        """
        a dict and writes them to the array
        format {channel:value}
        """
        for ch in message:
            self.dmx_message[ch] = message[ch]

    def write_frame(self):
        """
        Send a DMX frame
        """
        self.dmx_uart.value = 0
        time.sleep(88E-6)
        self.dmx_uart.value = 1
        self.dmx_uart.deinit()
        time.sleep(8E-6)
        self.dmx_uart = busio.UART(tx=board.TX,rx=None, baudrate=250000, bits=8, parity=None, stop=2)
        self.dmx_uart.write(self.dmx_message)
        self.dmx_uart.deinit()
        self.dmx_uart = digitalio.DigitalInOut(board.TX)
        self.dmx_uart.direction = digitalio.Direction.OUTPUT
        self.dmx_uart.value = 1

channel = 10

dmx = DMX(channel)
#dmx.set_channels({1:0})
for i in range(1,channel):
    print(i)
    dmx.set_channels({i:255})
    dmx.write_frame()
    time.sleep(2)
DatanoiseTV commented 1 year ago

It has been stale for over 2y. Is there any news on this?

s-light commented 1 year ago

currently i have no use-cases for dmx... otherwise i would try the fragment from @Tasm-Devil. seems like a valid first starting point...

wallarug commented 1 year ago

It has been stale for over 2y. Is there any news on this?

Just to confirm, is all that people want is a CircuitPython Library that does DMX control? Nothing more that this?

DatanoiseTV commented 1 year ago

@wallarug I would like to see a library which sends DMX packets periodically (min. 0.8 Frames/s, as per DMX Standard).

s-light commented 1 year ago

It has been stale for over 2y. Is there any news on this?

Just to confirm, is all that people want is a CircuitPython Library that does DMX control? Nothing more that this?

if i think about it for a moment - i can imagine three simple scenarios:

  1. DMX receiving - so the CP device can act as device in an event/stage enviroment
  2. DMX sending - simple just some channels in a buffer that get send manually or automatically every x ms
  3. DMX sending - API compatible to use it with FancyLED - maybe with a additionall mapping layer - so we could use this to integrate dmx-led-spots or similar things in an FancyLED Project....

as told - just a quick brainstorming... and i think the sketch from Tasm-Devil is a good starting point for 2.

mydana commented 1 year ago

I’m working on two libraries, one for DMX sending, and the other for DMX receiving. Both use RP2040’s PIO. They both work. I’m now doing some clean-up and hope I can get them into the Community Bundle.

wallarug commented 1 year ago

I’m working on two libraries, one for DMX sending, and the other for DMX receiving. Both use RP2040’s PIO. They both work. I’m now doing some clean-up and hope I can get them into the Community Bundle.

Awesome! Can you provide some GitHub links when you are ready?

dhalbert commented 1 year ago

I’m working on two libraries, one for DMX sending, and the other for DMX receiving. Both use RP2040’s PIO. They both work. I’m now doing some clean-up and hope I can get them into the Community Bundle.

We are really pleased to hear this!

mydana commented 1 year ago

https://github.com/mydana/CircuitPython_DMX_Transmitter Would love feedback.

wallarug commented 1 year ago

https://github.com/mydana/CircuitPython_DMX_Transmitter Would love feedback.

That looks awesome. I'll take another look soon and comment in the issues,

kylefmohr commented 7 months ago

https://github.com/mydana/CircuitPython_DMX_Transmitter Would love feedback.

Just wanted to say thanks @mydana, I just found your library from this thread and was able to set it up and get it working without any issues using an RP2040 and CircuitPython 9.0.0 beta 2. Your wiring diagram in the README was also very helpful. Thank you! I hope that it can be added to the community bundle soon.

mydana commented 4 months ago

https://github.com/mydana/CircuitPython_DMX_Receiver

It's still a bit buggy, but I'd love feedback.

tschundler commented 1 month ago

I independently came up with something very similar to https://github.com/adafruit/circuitpython/issues/673#issuecomment-616831705 from @Tasm-Devil switching the transmit pin between digital out (for Break + Mark-after-Break) and uart (for start condition + data)

That works on the ESP32s3 I use.

Notable difference is that time.sleep has a minimum increment of 1ms. So asking it to sleep 8uS for the Mark-after-break ends up being really 1ms. But initializing the uart alone takes more than the minimum MAB time, so I just set the pin high and don't sleep. (maybe dangerous is future micro python runs much faster. I wish there were a sleep_us(int))


I also experimented with using SPI to send out the bitstream. That allows very precise time control, but didn't really make any difference in my testing.

maxaitel commented 3 weeks ago

https://github.com/mydana/CircuitPython_DMX_Transmitter Would love feedback.

Why won't circup see your package? @mydana

dmx_transmitter is not a known CircuitPython library.

:(

I'm on a pico plus 2 running CircuitPython 9.

tannewt commented 2 weeks ago

Why won't circup see your package? @mydana

I needs to be added to the community bundle for circup to see it: https://github.com/adafruit/CircuitPython_Community_Bundle/tree/main/libraries/helpers