jostlowe / Pico-DMX

A library for inputting and outputting the DMX512-A lighting control protocol from a Raspberry Pi Pico
BSD 3-Clause "New" or "Revised" License
193 stars 22 forks source link

This library in rust #49

Closed JonasFocke01 closed 1 year ago

JonasFocke01 commented 1 year ago

Hello there,

im tinkering around with my rpi-pico and want to set it up as a dmx receiver something. Your library works great when in use with C/C++, but im also uinterested in the Rust lang. I got my pico working in rust, and even write onto a WS2812b strip, but the dmx input part wont work. I realy tried to work something out, that the garbage im receiving is interpreted as valid dmx, but i cant. I tried to learn from the sourcecode of this lib, but im somewhat stuck.. The input im getting by setting the GPIO0 to uart with 250000 hz, eight data and two stop bits seems realy scuffed. Are there any hints you could give me to succeed with this project? OC im willing to share any accomplishements i have made so far and will do in the future. Thanks for the help!

jostlowe commented 1 year ago

Hi!

As a "Rust enjoyer" this warms my heart!

There are a number of factors that might make the UART recieve garbage data. Have you checked:

I attempted to make a version of this library in Rust, but i hit one major snag: DMA is not fully supported in the rp2040-hal quite yet, which makes it difficult to make a memory safe version of this library in Rust. Despite the DMA issues, it is definitely still possible to leverage part of PicoDMX in Rust!

The library primarily consists of two parts: The PIO assembly code and the C code that interracts with the PIO to push or pull the DMX frames out of the PIO. (This is what is solved using DMA in this project). The PIO assembly code can be used "as is" in a rust project. In that case, all you have to solve in Rust is the pushing or pulling part of the code.

If you are receiving DMX frames, then the Rust code might be structured something like this:

  1. Configure the PIO with the assembly code and configurations from PicoDMX
  2. Run the PIO
  3. Pull the appropriate amount of data from the RX FIFO of the PIO (By either just waiting for the data or with a DMA if you are feeling adventurous)
  4. Reset the PIO so that it is ready for the next incoming DMX frame
  5. Go to step 2, rinse and repeat

I hope this makes some sense!

JonasFocke01 commented 1 year ago

Thank you for the reply :heart: Its great to hear, that there is the possibility to get this working! Im not yet that deep into the pico, so it blew my mind, that the assembly actually in some sort of a state machine, that can be used to configure the pio, and not just some auto generated compiler thingy! Im looking realy forward to tinker with this :smile: As im not thaat skilled of a embeded dev, that could take some time, but im on it! And i will let you know how this went!

jostlowe commented 1 year ago

Excellent! I'm looking forwards to hearing about your results :)

JonasFocke01 commented 1 year ago

So i got at least something working. The sm seems to load the program (DMXInput), and starts reading. It in fact reads correctly, that a channel is set to something other than 0, but that is where the good news stop.. The program spits out random values (from 00 to ff). I was able to confirm, that my max485 is configured correctly by trying it with an arduino. I have hooked it up to gpio22 through a llc to get from 5V to the 3.3V.

Could you take a look at the code i currently have? I suspect, that i have done something wrong with the clock frequency, but i dont realy know.. In my code is a USB driver configured, that writes the fifo result to usb to debug it.

#![no_std]
#![no_main]

use rp2040_hal::{clocks::init_clocks_and_plls, gpio::Pins, pac, sio::Sio};
use rp_pico::entry;
use hal::pio::PIOExt;
use panic_halt as _;
use rp_pico::hal;
use usb_device::{class_prelude::*, prelude::*};
use usbd_serial::SerialPort;

const XOSC_CRYSTAL_FREQ: u32 = 12_000_000;

#[entry]
fn main() -> ! {
    let mut pac = pac::Peripherals::take().unwrap();

    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);

    let sio = Sio::new(pac.SIO);
    let pins = Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );
    let clocks = init_clocks_and_plls(
        XOSC_CRYSTAL_FREQ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    // Setup PIO
    pins.gpio22.into_pull_down_input();

    let program_with_defines = pio_proc::pio_file!("DmxInput.pio", select_program("DmxInput"),);

    let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
    let installed = pio.install(&program_with_defines.program).unwrap();
    let (sm, mut rx, _) = rp2040_hal::pio::PIOBuilder::from_program(installed)
        .jmp_pin(22)
        .in_pin_base(22)
        .clock_divisor_fixed_point(48, 0)
        .build(sm0);
    sm.start();

    // Set up the USB driver
    let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
        pac.USBCTRL_REGS,
        pac.USBCTRL_DPRAM,
        clocks.usb_clock,
        true,
        &mut pac.RESETS,
    ));

    let mut serial = SerialPort::new(&usb_bus);

    UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
        .manufacturer("Fake company")
        .product("Serial port")
        .serial_number("TEST")
        .device_class(2)
        .build();

    loop {
        if let Some(channel) = rx.read() {
            channel.to_le_bytes().iter().for_each(|channel| {
                if *channel != 0 {
                    serial.write(&channel.to_le_bytes()).unwrap();
                }
            });
        }
    }
}
jostlowe commented 1 year ago

Hi! Have you had any progress on this?

My first immediate thoughts are these:

JonasFocke01 commented 1 year ago

Hi!

So i made some progress, i think, but it feels like, that the path is a steep one and im far from finished :laughing:
I double, triple, quadruple checked the configured clock speed and read a ton about this on the internet, and i think, this is correct now. The stuff it reads is still garbage, but consistent garbage :partying_face: , like 'one channel on 100 means it reads 30 indefinitely, but if i move the channel to 101, it suddenly reads 213 indefinitely' (those are random numbers and change each time i try it)
Same occurs with two or three channels...
I also tried to figure out how i could set a word-bit-size, but i was not able to. Im dividing the u32 that the read() function on the rx buffer gives me into four bytes anyway, so im a bit curious, what this would do? For the last bulletpoint, i added an index to my program, that counts to 512 and restarts the sm, but that seems to make the problem worse again, as that adds some significant noise to what the pico is reading.

Can you make any sense of that? Does that sound familiar in some way to you?

jostlowe commented 1 year ago

Hi!

Maybe this can be of help: https://github.com/jostlowe/picodmx-rs

Note that this in its infancy and is very much a WIP.

JonasFocke01 commented 1 year ago

Thank you very much!
That is very cool and runs well on my machine! (after some toolchain tweaking oc... :D)