This shows you the differences between two versions of the page.
— |
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> |