// - - - - -
// DMXSerial - A Arduino library for sending and receiving DMX using the builtin serial hardware port.
// DMXSerial.cpp: Library implementation file
//
// Copyright (c) 2011 by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
//
// Documentation and samples are available at http://www.mathertel.de/Arduino
// Changelog: See DMXSerial.h
// - - - - -
// Define port & bit values for Hardware Serial Port.
// The library works unchanged with the Arduino 2009, UNO, MGEA 2560 and Leonardo boards.
// The Arduino MGEA 2560 boards use the serial port 0 on pins 0 an 1.
// The Arduino Leonardo will use serial port 1, also on pins 0 an 1. (on the 32u4 boards the first USART is USART1)
// This is consistent to the Layout of the Arduino DMX Shield http://www.mathertel.de/Arduino/DMXShield.aspx.
// For using the serial port 1 on a Arduino MEGA 2560 board, enable the following DMX_USE_PORT1 definition.
// #define DMX_USE_PORT1
#if !defined(DMX_USE_PORT1) && defined(USART_RXC_vect)
// These definitions are used on ATmega8 boards
#define UCSRnA UCSRA // Control and Status Register A
#define TXCn TXC // Transmit buffer clear
#define UCSRnB UCSRB // USART Control and Status Register B
#define UDRn UDR // USART Data Register
#define UDREn UDRE // USART Data Ready
#define FEn FE // Frame Error
#define USARTn_RX_vect USART_RXC_vect // Interrupt Data received
#define USARTn_TX_vect USART_TXC_vect // Interrupt Data sent
#define USARTn_UDRE_vect USART_UDRE_vect // Interrupt Data Register empty
// the break timing is 10 bits (start + 8 data + parity) of this speed
// the mark-after-break is 1 bit of this speed plus approx 6 usec
// 100000 bit/sec is good: gives 100 usec break and 16 usec MAB
// 1990 spec says transmitter must send >= 92 usec break and >= 12 usec MAB
// receiver must accept 88 us break and 8 us MAB
#define BREAKSPEED 100000
#define DMXSPEED 250000
#define BREAKFORMAT SERIAL_8E1
#define DMXFORMAT SERIAL_8N2
// State of receiving DMX Bytes
typedef enum {
STARTUP = 1, // wait for any interrupt BEFORE starting anylyzig the DMX protocoll.
IDLE = 2, // wait for a BREAK condition.
BREAK = 3, // BREAK was detected.
DATA = 4, // DMX data.
DONE = 5 // All channels received.
} __attribute__((packed)) DMXReceivingState;
// calculate prescaler from baud rate and cpu clock rate at compile time
// nb implements rounding of ((clock / 16) / baud) - 1 per atmega datasheet
#define Calcprescale(B) ( ( (((F_CPU)/8)/(B)) - 1 ) / 2 )
// ----- DMXSerial Private variables -----
// These variables are not class members because they have to be reached by the interrupt implementations.
// don't use these variable from outside, use the appropriate methods.
DMXMode _dmxMode; // Mode of Operation
int _dmxModePin; // pin used for I/O direction.
uint8_t _dmxRecvState; // Current State of receiving DMX Bytes
int _dmxChannel; // the next channel byte to be sent.
volatile unsigned int _dmxMaxChannel = 32; // the last channel used for sending (1..32).
volatile unsigned long _dmxLastPacket = 0; // the last time (using the millis function) a packet was received.
bool _dmxUpdated = true; // is set to true when new data arrived.
// Array of DMX values (raw).
// Entry 0 will never be used for DMX data but will store the startbyte (0 for DMX mode).
uint8_t _dmxData[DMXSERIAL_MAX+1];
// This pointer will point to the next byte in _dmxData;
uint8_t *_dmxDataPtr;
// This pointer will point to the last byte in _dmxData;
uint8_t *_dmxDataLastPtr;
// Create a single class instance. Multiple class instances (multiple simultaneous DMX ports) are not supported.
DMXSerialClass DMXSerial;
// (Re)Initialize the specified mode.
// The mode parameter should be a value from enum DMXMode.
void DMXSerialClass::init(int mode, int dmxModePin)
{
// initialize global variables
_dmxMode = DMXNone;
_dmxModePin = dmxModePin;
_dmxRecvState = STARTUP; // initial state
_dmxChannel = 0;
_dmxDataPtr = _dmxData;
_dmxLastPacket = millis(); // remember current (relative) time in msecs.
_dmxMaxChannel = DMXSERIAL_MAX; // The default in Receiver mode is reading all possible 512 channels.
_dmxDataLastPtr = _dmxData + _dmxMaxChannel;
// initialize the DMX buffer
// memset(_dmxData, 0, sizeof(_dmxData));
for (int n = 0; n < DMXSERIAL_MAX+1; n++)
_dmxData[n] = 0;
// now start
_dmxMode = (DMXMode)mode;
if ((_dmxMode == DMXController) || (_dmxMode == DMXReceiver) || (_dmxMode == DMXProbe)) {
// a valid mode was given
// Setup external mode signal
pinMode(_dmxModePin, OUTPUT); // enables the pin for output to control data direction
digitalWrite(_dmxModePin, DmxModeIn); // data in direction, to avoid problems on the DMX line for now.
if (_dmxMode == DMXController) {
digitalWrite(_dmxModePin, DmxModeOut); // data Out direction
_dmxMaxChannel = 32; // The default in Controller mode is sending 32 channels.
_DMXStartSending();
// Set the maximum used channel.
// This method can be called any time before or after the init() method.
void DMXSerialClass::maxChannel(int channel)
{
if (channel < 1) channel = 1;
if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
_dmxMaxChannel = channel;
_dmxDataLastPtr = _dmxData + channel;
} // maxChannel
// Read the current value of a channel.
uint8_t DMXSerialClass::read(int channel)
{
// adjust parameter
if (channel < 1) channel = 1;
if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
// read value from buffer
return(_dmxData[channel]);
} // read()
// Write the value into the channel.
// The value is just stored in the sending buffer and will be picked up
// by the DMX sending interrupt routine.
void DMXSerialClass::write(int channel, uint8_t value)
{
// adjust parameters
if (channel < 1) channel = 1;
if (channel > DMXSERIAL_MAX) channel = DMXSERIAL_MAX;
if (value < 0) value = 0;
if (value > 255) value = 255;
// store value for later sending
_dmxData[channel] = value;
// Make sure we transmit enough channels for the ones used
if (channel > _dmxMaxChannel) {
_dmxMaxChannel = channel;
_dmxDataLastPtr = _dmxData + _dmxMaxChannel;
} // if
} // write()
// Calculate how long no data packet was received
unsigned long DMXSerialClass::noDataSince()
{
unsigned long now = millis();
return(now - _dmxLastPacket);
} // noDataSince()