Arduinoesque overriding of core functionality

Status
Not open for further replies.

Ward

Member
I'm working on a project that uses UART0 for reception of DMX512 serial data and have everything working at this point, but in order to do that I had to significantly modify a few core files: mk20dx128.h, serial1.c, and hardwareserial.h in order to change the uart status ISR and serial buffer structure. I could distribute the code as-is, with instructions to replace those files with my own, but that would semi-permanently change the behavior of the core code, which I consider to be bad form. I was wondering if there's provision within the Arduino (or Teensy) build system to provisionally replace those sections of code without resorting to modification of any of the default installation. I note that that's generally done for ISRs using the __attribute__ keyword to weakly bind default code segments, but once the ISR's been established within serial1.c it looks to be a strong binding. Are there any other options?
 
I suppose one option would be to distribute new files with a bunch of #ifdefs or similar. That would prevent breaking the default functionality, but would still require touching the core code.
 
I am willing to add code to Teensy's core library, to support break detection for the sake of DMX receive. Ideally it should not make the Serial any slower for normal programs that do not require break detection. I'm sure it will increase the code size, but hopefully that can be minimized.

I'm planning to release 1.13 within the next week or two. Do you have something I should consider merging for 1.13?
 
Thanks for the quick response...on a Sunday morning, no less! The break detection's actually using the UART's frame error detection to trigger on the missing stop bit. It's a hack, but if you're seeing frame errors that aren't due to the break then you've got bigger problems. At any rate, when the frame error interrupt trips, the ISR resets the buffer pointer. It's really straightforward, and that part doesn't require touching the core serial library.

I think the biggest change to the core code is that I'm using a 513 byte buffer that's *not* a ring buffer...if the index runs over for some reason, it spins on buffer[512] to prevent poisoning the rest of the buffer. That's pretty different from your 32 byte ring buffer implementation, which makes perfect sense for general-purpose serial receiving. I don't know what the best way to implement this is, since it's all done in the UART status ISR and can't be overridden. Thoughts?
 
I should also add that it's possible to do all of this within the existing structure, just by reading bytes out of the ring buffer and putting them in the DMX buffer. Maintaining two buffers and touching the bytes twice is a bit more expensive but a lot less intrusive. I could certainly be accused of going down the engineer's route of stacking angels on the head of a pin while ignoring the surrounding empty football field. There might also be some nasty race conditions introduced by that approach.
 
I'm hoping for minimally intrusive changes.

At 250 kbps and 11 bits per byte (1 start, 8 data, 2 stop), each byte takes 44 us. That's plenty of time for a little overhead moving the data between buffers(**). Currently serial1.c has a 64 byte buffer. That's 2.8 ms to overflow the buffer, so you won't be able to do stuff like delay(100). But it should be easily possible to build a DMX controller with Serial1.read() and controls lights as it gets the data, or piles the data up into a 512 byte buffer and uses it when the transmission ends.

I must confess, I haven't done testing with receiving break signals. My understanding is the existing code should return a single 0 byte for the entire break. Is that right?

From an API point of view, I can imagine a few ways this might work. Probably the simplest would be a callback function, similar to attachInterrupt(), called when the hardware detects a break. Maybe a reasonable implementation would call that function if there's a framing error and the byte received is zero? Then in your callback function, you could use Serial1.clear() and set up anything your DMX parsing code needs. The one downside is this burdens your application code with interrupt safety issues, but maybe that's ok since this is mostly meant for supporting one protocol.



** - on the buffering overhead, probably the worst issue, and one that I'm planning to address eventually, is the lack of an efficient block read function in the Arduino API. DMX is slow enough that the currently available APIs should be fine. Unfortunately, this sort of change probably requires coordinating with the Arduino Team on the official Arduino API... which takes a lot of time to first make the case in public, and once the decision is made, the only way to move forward at any speed is pretty much doing all the work in a pull request for all official Arduino boards too. I might do that someday, but right now I have a lot of other more urgent things (and break support for DMX is on that list).
 
All done. It's a shame DorkBot's not tomorrow night, or I'd haul my lighting console down and use it to flash some LEDs using the DMX library and OctoWS2811. To answer your question about breaks, yes, the UART core code registers the break as 0x00.

The implementation details:
I did pretty much what you suggest above. I'm actually using two buffers, an "active" one that's being written, and an "inactive" one that contains the contents of the last frame. When each frame completes, the buffers toggle state. This can be done with a single buffer if the user's not concerned with mixing data from two frames.

Functions in the library:

void dmx_begin(void);
Initializes the serial port, sets up the UART error interrupt

void dmx_end(void);
Terminates the serial port, disables the UART error interrupt

int dmx_bufferService(void);
Transfers bytes from the UART receive FIFO to the DMX buffer. CALL EVERY 2.5MS OR LESS OR THE FRAME DATA WILL BE CORRUPTED. Returns the number of bytes transferred; if it's 64, best to discard the frame. Blocks interrupts for 0-100us.

uint8_t dmx_getDimmer(uint16_t);
Get an individual dimmer value

unsigned int dmx_frameCount(void);
Get the frame count (can be useful to see if you're dropping frames for some reason)

bool dmx_newFrame(void);
Returns true if a new frame has been received since the function was last called
 

Attachments

  • DmxReceiver.cpp
    1.8 KB · Views: 1,481
  • DmxReceiver.h
    731 bytes · Views: 1,294
  • DMX.ino
    273 bytes · Views: 1,521
I think the biggest change to the core code is that I'm using a 513 byte buffer that's *not* a ring buffer...if the index runs over for some reason, it spins on buffer[512] to prevent poisoning the rest of the buffer. That's pretty different from your 32 byte ring buffer implementation, which makes perfect sense for general-purpose serial receiving. I don't know what the best way to implement this is, since it's all done in the UART status ISR and can't be overridden. Thoughts?

It would be a great feature if you could set the buffer size for the receiving part.
That is you do not always need to read the whole 512 size dmx chunk.
The parameters might as well be located in the first 20 or so bytes.
void init_dmx_read(buffersize)

.F
 
It would be a great feature if you could set the buffer size for the receiving part.
That is you do not always need to read the whole 512 size dmx chunk.
The parameters might as well be located in the first 20 or so bytes.
void init_dmx_read(buffersize)

.F

An excellent point. It's actually pretty rare to need all 512 channels' information, so keeping tying up 1k of memory for it is pretty wasteful. If I get a chance at some point I'll add another constructor that allows a subset range of channels.
 
An excellent point. It's actually pretty rare to need all 512 channels' information, so keeping tying up 1k of memory for it is pretty wasteful. If I get a chance at some point I'll add another constructor that allows a subset range of channels.

A more extended function might be to provide a list of addresses for which only input will be returned.
e.g. for the address list [1, 2, 3, 42, 123, 420] the interrupt routine will pick out the cherries and return a list of 6 data.
 
is it possible that this stops other intervalTimer from being called?
i am writing code for the teensy 3 that dims 16 leds, which works great.
and the dmxreceiver works great too.
but when i put them both together i do receive any dmx.

thanks.

Code:
IntervalTimer dimmingTimer;

void dimming_handler2(void) {
//some dimming code here that uses  digitalWriteFast(ledPin[i],dimStates[i]);
}

void setup_dimming(){
  dimmingTimer.begin(dimming_handler2, 30);  
}
 
serial port ring buffer schemes I've done use a ring buffer with an error bit 'buffer overflow' that can be accessed.

Great debate, and perhaps a run time option choice, on whether to wrap the buffer (ring) or discard data when buffer fills. Making it a run time option lets the app choose.
Some systems also let the app provide the buffer at port open time, in case the app wants an unusually large buffer.
 
Thankyou for the code. I have been playing with it for a week, but I'm getting intermittent results from a commercial artnet-dmx node.

I borrowed an oscilloscope and learnt about scoping differential signals the hard way. All signals look clean, the 485 transceiver appears to be doing its job, the data is coming into the teensy cleanly and at a reasonable level (my 7407 step down circuit was suffering capacitance issues due to large pull up resistors - fixed now, but same result. Also used a voltage divider to stop the 5v down to 3.3v. No dice). I also tried multiple power supplies, extra bypass caps, etc.

Today I borrowed another DMX generating device (like a riggers remote), and everything works perfectly with this! Not so with the artnet node.

I've done some more trouble shooting with sending a channel value back to the serial port - the artnet node often gives alternate values on each frame - ie 40 170 40 170 40 etc. My guess is that this could be something to do with double counting the break, and hence double-swapping registers? I've used the node previously with other devices and it has performed fine (unless I've damaged it through my testing).

Can anyone think of anything else I am missing here?

Edit - looking at the scope again, it looks like the break, MAB and slot 0 are all inverted on the artnet node - but then the packets are all fine. I can see how this could confuse the UART. I suppose I should start looking at the artnet node output stage as a first port of call.
 
Last edited:
Call off the panic... I just opened up the node and the 485 showed some signs of stress... I probed the DI line and the break and MAB were the correct way around! I connected gnd and the DI line to the teensy directly, and hey-presto, it worked. Guess I'm off to order a new rs485 driver for the node...
 
When i use this code, it works fine with a simple DMX tester, but when i use it with a art-net to DMX node, the values aren't constant. i get this values ;

Code:
37
37
37
37
37
37
37
0
37
37
37
37
37
0
37
37
0
37
0
37
0
0
37
0
37
37
0
37
0
0
37
0
37
0
37
0
0
0

how can i solve this?
 
hi, i have a problem with dmx Receiver library, the problem is:
with frystyler 512 i set channel 1 to 40 and channel 2 to 30, when i read channel 2 with teensy 3.1 using the arduino ide i go to serial monitoring and i see 30, but one time i see the value of channel 2 end one time the value of chanel 1 exaple:
i see in serial monitoring on arduino ide: 30 40 30 40 30 40 30 40, if i change the value of channel 1 from 40 to 77 i see 30 77 30 77 30 77, i only read the channel 2 but it read channel 2 and channel 1
any solution? i use max485 for convert dmx signal to rs232 and its set ro recive mode onli, (pin 2, 3 to gnd, pin 1 to rx of teensy, pin 6 to dmx pin 3 and pin 7 to dmx pin 2)
thanks
 
Hi,

Ward's code has problems with any DMX controller that doesn't have a long MARK time between the last DMX value and the BREAK. Chauvet brand controllers work fine, but my uDMX USB adapter does not. The symptom is that the values in the DMX buffer will be randomly shifted by 1-4 addresses, causing wild flickering and whatnot.

The problem is because of an interaction between the Teensy software buffer, the hardware FIFO, and Ward's new code. Briefly, a frame error interrupt will be generated whenever a BREAK occurs. In the frame error ISR, any leftover previously-received data is processed by calling dmx_bufferService. However, this only retrieves data that has already been stored into software buffers by the Teensy uart0_status_isr. There may still be other data in the hardware RX FIFO, since
the default configuration for the Teensy allows up to 4 bytes to get queued before the uart0_status_isr is fired. Right now, that data ends up in the wrong place, the new frame.

With some controllers, this is not a problem because uart0_status_isr is also fired during a long idle, which may allow the receive FIFO to have been emptied immediately before the BREAK. But this idle period ("mark time between packets" in DMX spec) has no minimum length, so it's perfectly valid for controllers to leave it out.

Sticking with Ward's approach of trying to work with what we've got, one approach is to set UART0_RWFIFO=1 so that the Teensy uart0_status_isr handler is triggered immediately after each byte. Then some small changes are needed to our uart0_error_isr, since the frame error may already get cleared by the status ISR; other than that it's basically the same.

Of course, the best solution would be to integrate uart0_error_isr into the Teensy code so that breaks are properly detected and handled in sync with the status ISR.

I've updated and cleaned up the DmxReceiver code, repackaged it into an Arduino library with example code, and uploaded it to Github: https://github.com/jimparis/DmxReceiver. I also took the liberty of slapping the usual Teensy (MIT) license on the files. Ward, if you don't agree with that, just let me know and I'll fix it.
 
After working on a DMX project that I wanted send and receive I took the above DmxReceiver library, added in the DmxSimple library and then added RDM from the DMXSerial2 library. I then modified the TX code to make use of the hardware rather than doing bit-banging. It seems to work using the limited hardware I have lying around the house, but would appreciate people testing and developing more.

Since it's in a relatively untested state I've not included an examples directory for not to dissuade people who don't know what they're doing from using the library until it's more stable.

Library is available at: https://github.com/chrisstaite/TeensyDmx - I've adopted the same MIT license. I don't think that breaks anyone else's license that I've copied code from.
 
this looks very interesting chris.
do you think you could also upload an example sketch to github for dmx send, receive and RDM?

thanks.
 
Status
Not open for further replies.
Back
Top