User Tools

Site Tools


dmx-simple-cpp

Warning: Use of undefined constant PREG_PATTERN_VALID_LANGUAGE - assumed 'PREG_PATTERN_VALID_LANGUAGE' (this will throw an Error in a future version of PHP) in /var/www/kucjica/emperors-wiki/inc/parser/xhtml.php on line 633

Warning: preg_replace(): Delimiter must not be alphanumeric or backslash in /var/www/kucjica/emperors-wiki/inc/parser/xhtml.php on line 633

DmxSimple.cpp

/**
 * DmxSimple - A simple interface to DMX.
 *
 * Copyright (c) 2008-2009 Peter Knight, Tinker.it! All rights reserved.
 */

/** 
 * Some alterations by Andreas Rothenwänder
 *   
 *   * added support for AT90USB162, AT90USB646 & AT90USB1286 (e.g. Teensy++ board - suggested by Paul Stoffregen)
 *   * write   : added return value - returns now previous setting
 *   * usePin  : minor bug corrected
 *   * modulate: new function - relative value change of given channel (returns new value)
 *   * getValue: new function - returns current value of given channel
 *   
 *   alterations commented as // --> 
 */


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "pins_arduino.h"

#include "Arduino.h"
#include "DmxSimple.h"

/** dmxBuffer contains a software copy of all the DMX channels.
  */
volatile uint8_t dmxBuffer[DMX_SIZE];
static uint16_t dmxMax = 16; /* Default to sending the first 16 channels */
static uint8_t dmxStarted = 0;
static uint16_t dmxState = 0;

static volatile uint8_t *dmxPort;
static uint8_t dmxBit = 0;
static uint8_t dmxPin = 3; // Defaults to output on pin 3 to support Tinker.it! DMX shield

void dmxBegin();
void dmxEnd();
void dmxSendByte(volatile uint8_t);
uint8_t dmxWrite(int,uint8_t);
void dmxMaxChannel(int);
uint8_t dmxModulate(int, int);
uint8_t dmxGetValue(int);

/* TIMER2 has a different register mapping on the ATmega8.
 * The modern chips (168, 328P, 1280) use identical mappings.
 */
// --> added "|| defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)"
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
#define TIMER2_INTERRUPT_ENABLE() TIMSK2 |= _BV(TOIE2)
#define TIMER2_INTERRUPT_DISABLE() TIMSK2 &= ~_BV(TOIE2)
#elif defined(__AVR_ATmega8__) 
#define TIMER2_INTERRUPT_ENABLE() TIMSK |= _BV(TOIE2)
#define TIMER2_INTERRUPT_DISABLE() TIMSK &= ~_BV(TOIE2)
// --> -----------------------------
// --> added section for AT90USB162 (this CPU has only one timer - can cause problems)
#elif defined(__AVR_AT90USB162__) // added 
#define TIMER2_INTERRUPT_ENABLE() TIMSK0 |= _BV(TOIE0)
#define TIMER2_INTERRUPT_DISABLE() TIMSK0 &= ~_BV(TOIE0)
// --> -----------------------------
#elif defined (__AVR_ATmega32U4__) // leonardo
#define TIMER2_INTERRUPT_ENABLE() TIMSK4 |= _BV(TOIE4)
#define TIMER2_INTERRUPT_DISABLE() TIMSK4 &= ~_BV(TOIE4)
#else
#define TIMER2_INTERRUPT_ENABLE()
#define TIMER2_INTERRUPT_DISABLE()
/* Produce an appropriate message to aid error reporting on nonstandard
 * platforms such as Teensy.
 */
#warning "DmxSimple does not support this CPU"
#endif


/** Initialise the DMX engine
 */
void dmxBegin()
{
  dmxStarted = 1;

  // Set up port pointers for interrupt routine
  dmxPort = portOutputRegister(digitalPinToPort(dmxPin));
  dmxBit = digitalPinToBitMask(dmxPin);

  // Set DMX pin to output
  pinMode(dmxPin,OUTPUT);

  // Initialise DMX frame interrupt
  //
  // Presume Arduino has already set Timer2 to 64 prescaler,
  // Phase correct PWM mode
  // So the overflow triggers every 64*510 clock cycles
  // Which is 510 DMX bit periods at 16MHz,
  //          255 DMX bit periods at 8MHz,
  //          637 DMX bit periods at 20MHz
// --> -----------------------------
// --> added section for AT90USB646/1286
#if defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__)
  TCCR2A = (1<<WGM20);
  TCCR2B = (1<<CS22); // 64 prescaler
#elif defined (__AVR_ATmega32U4__) //leonardo
  // comme c'est un timer 10 bits, on mets le prescaler à 16
 // TCCR4A =
  TCCR4B = (1<<CS42) | (1<<CS40);

#endif
// --> -----------------------------
  TIMER2_INTERRUPT_ENABLE();
}

/** Stop the DMX engine
 * Turns off the DMX interrupt routine
 */
void dmxEnd()
{
  TIMER2_INTERRUPT_DISABLE();
  dmxStarted = 0;
  dmxMax = 0;
}

/** Transmit a complete DMX byte
 * We have no serial port for DMX, so everything is timed using an exact
 * number of instruction cycles.
 *
 * Really suggest you don't touch this function.
 */
void dmxSendByte(volatile uint8_t value)
{
  uint8_t bitCount, delCount;
  __asm__ volatile (
    "cli\n"
    "ld __tmp_reg__,%a[dmxPort]\n"
    "and __tmp_reg__,%[outMask]\n"
    "st %a[dmxPort],__tmp_reg__\n"
    "ldi %[bitCount],11\n" // 11 bit intervals per transmitted byte
    "rjmp bitLoop%=\n"     // Delay 2 clock cycles. 
  "bitLoop%=:\n"\
    "ldi %[delCount],%[delCountVal]\n"
  "delLoop%=:\n"
    "nop\n"
    "dec %[delCount]\n"
    "brne delLoop%=\n"
    "ld __tmp_reg__,%a[dmxPort]\n"
    "and __tmp_reg__,%[outMask]\n"
    "sec\n"
    "ror %[value]\n"
    "brcc sendzero%=\n"
    "or __tmp_reg__,%[outBit]\n"
  "sendzero%=:\n"
    "st %a[dmxPort],__tmp_reg__\n"
    "dec %[bitCount]\n"
    "brne bitLoop%=\n"
    "sei\n"
    :
      [bitCount] "=&d" (bitCount),
      [delCount] "=&d" (delCount)
    :
      [dmxPort] "e" (dmxPort),
      [outMask] "r" (~dmxBit),
      [outBit] "r" (dmxBit),
      [delCountVal] "M" (F_CPU/1000000-3),
      [value] "r" (value)
  );
}

/** DmxSimple interrupt routine
 * Transmit a chunk of DMX signal every timer overflow event.
 * 
 * The full DMX transmission takes too long, but some aspects of DMX timing
 * are flexible. This routine chunks the DMX signal, only sending as much as
 * it's time budget will allow.
 *
 * This interrupt routine runs with interrupts enabled most of the time.
 * With extremely heavy interrupt loads, it could conceivably interrupt its
 * own routine, so the TIMER2 interrupt is disabled for the duration of
 * the service routine.
 */
#if defined (__AVR_ATmega32U4__)
ISR(TIMER4_OVF_vect,ISR_NOBLOCK) 
#else
ISR(TIMER2_OVF_vect,ISR_NOBLOCK)
#endif 
{

  // Prevent this interrupt running recursively
  TIMER2_INTERRUPT_DISABLE();

  uint16_t bitsLeft = F_CPU / 31372; // DMX Bit periods per timer tick
  bitsLeft >>=2; // 25% CPU usage
  while (1) {
    if (dmxState == 0) {
      // Next thing to send is reset pulse and start code
      // which takes 35 bit periods
      uint8_t i;
      if (bitsLeft < 35) break;
      bitsLeft-=35;
      *dmxPort &= ~dmxBit;
      for (i=0; i<11; i++) _delay_us(8);
      *dmxPort |= dmxBit;
      _delay_us(8);
      dmxSendByte(0);
    } else {
      // Now send a channel which takes 11 bit periods
      if (bitsLeft < 11) break;
      bitsLeft-=11;
      dmxSendByte(dmxBuffer[dmxState-1]);
    }
    // Successfully completed that stage - move state machine forward
    dmxState++;
    if (dmxState > dmxMax) {
      dmxState = 0; // Send next frame
      break;
    }
  }
  
  // Enable interrupts for the next transmission chunk
  TIMER2_INTERRUPT_ENABLE();
}

uint8_t dmxWrite(int channel, uint8_t value) {  // --> uint8_t replaces void as return value
  uint8_t oldValue = 0;                         // --> buffer for return value
  if (!dmxStarted) dmxBegin(); 
  if ((channel > 0) && (channel <= DMX_SIZE)) {
    dmxMax = max((unsigned)channel, dmxMax); 
    oldValue = dmxBuffer[channel-1];            // --> remember previous value
    dmxBuffer[channel-1] = value; 
  } 
  return oldValue;                              // --> return previous value
} 

void dmxMaxChannel(int channel) {
  if (channel <=0) {
    // End DMX transmission
    dmxEnd();
    dmxMax = 0;
  } else {
    dmxMax = min(channel, DMX_SIZE);
    if (!dmxStarted) dmxBegin();
  }
}

// --> new function
uint8_t dmxModulate(int channel, int offset)
{
  if (!dmxStarted) dmxBegin();
  if ((channel > 0) && (channel <= DMX_SIZE)) {
    dmxMax = max((unsigned)channel, dmxMax); 
    return (dmxBuffer[channel-1] = max(min(offset + dmxBuffer[channel-1], 255), 0));
  }
  return 0;
}

// --> new function
uint8_t dmxGetValue(int channel)
{
  if ((channel > 0) && (channel <= DMX_SIZE))
    return (dmxBuffer[channel-1]);
  else
    return 0;
}

/* C++ wrapper */


/** Set output pin
 * @param pin Output digital pin to use
 */
void DmxSimpleClass::usePin(uint8_t pin) {
  bool restartRequired = dmxStarted;

// --> original version never set pin  
  if (restartRequired) 
    dmxEnd();

  dmxPin = pin;

  if (restartRequired) 
    dmxBegin();
}

/** Set DMX maximum channel
 * @param channel The highest DMX channel to use
 */
void DmxSimpleClass::maxChannel(int channel) {
  dmxMaxChannel(channel);
}

/** Write to a DMX channel
 * @param address DMX address in the range 1 - 512
 */
uint8_t DmxSimpleClass::write(int address, uint8_t value)       // --> uint8_t replaces void as return value
{
        return dmxWrite(address, value);                        // --> return previous value
}

// --> new function
uint8_t DmxSimpleClass::modulate(int channel, int offset)
{
  return dmxModulate(channel, offset);
}

// --> new function
uint8_t DmxSimpleClass::getValue(int channel)
{
  return dmxGetValue(channel);
}

DmxSimpleClass DmxSimple;
dmx-simple-cpp.txt · Last modified: 2014/02/05 12:50 by zoza