User Tools

Site Tools


dmx-simple-cpp

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

dmx-simple-cpp [2014/02/05 12:50] (current)
zoza created
Line 1: Line 1:
 +====== DmxSimple.cpp ======
  
 +<code java>
 +/**
 + * 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;
 +</​code>​
dmx-simple-cpp.txt · Last modified: 2014/02/05 12:50 by zoza