parent
73ccf04ead
commit
6eaab7ea62
@ -0,0 +1,293 @@ |
|||||||
|
/**
|
||||||
|
* DmxSimple - A simple interface to DMX. |
||||||
|
* |
||||||
|
* Copyright (c) 2008-2009 Peter Knight, Tinker.it! All rights reserved. |
||||||
|
*/ |
||||||
|
// modified to support all Teensy boards
|
||||||
|
#include <avr/io.h> |
||||||
|
#include <avr/interrupt.h> |
||||||
|
#include <util/delay.h> |
||||||
|
#include "pins_arduino.h" |
||||||
|
|
||||||
|
#include "Arduino.h" |
||||||
|
#include "DmxSimplePlus.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); |
||||||
|
void dmxWrite(int, uint8_t); |
||||||
|
void dmxMaxChannel(int); |
||||||
|
|
||||||
|
/* TIMER2 has a different register mapping on the ATmega8.
|
||||||
|
* The modern chips (168, 328P, 1280) use identical mappings. |
||||||
|
*/ |
||||||
|
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) |
||||||
|
#define TIMER2_INTERRUPT_ENABLE() TIMSK2 |= _BV(TOIE2) |
||||||
|
#define TIMER2_INTERRUPT_DISABLE() TIMSK2 &= ~_BV(TOIE2) |
||||||
|
#define ISR_NAME TIMER2_OVF_vect |
||||||
|
#define BITS_PER_TIMER_TICK (F_CPU / 31372) |
||||||
|
|
||||||
|
// older ATMEGA8 has slighly different timer2
|
||||||
|
#elif defined(__AVR_ATmega8__) |
||||||
|
#define TIMER2_INTERRUPT_ENABLE() TIMSK |= _BV(TOIE2) |
||||||
|
#define TIMER2_INTERRUPT_DISABLE() TIMSK &= ~_BV(TOIE2) |
||||||
|
#define ISR_NAME TIMER2_OVF_vect |
||||||
|
#define BITS_PER_TIMER_TICK (F_CPU / 31372) |
||||||
|
|
||||||
|
// ATMEGA32U4 on Teensy and Leonardo has no timer2, but has timer4
|
||||||
|
#elif defined(__AVR_ATmega32U4__) |
||||||
|
#define TIMER2_INTERRUPT_ENABLE() TIMSK4 |= _BV(TOIE4) |
||||||
|
#define TIMER2_INTERRUPT_DISABLE() TIMSK4 &= ~_BV(TOIE4) |
||||||
|
#define ISR_NAME TIMER4_OVF_vect |
||||||
|
#define BITS_PER_TIMER_TICK (F_CPU / 31372) |
||||||
|
|
||||||
|
// Teensy 3.0 has IntervalTimer, unrelated to any PWM signals
|
||||||
|
#elif defined(CORE_TEENSY) && defined(__arm__) |
||||||
|
#define TIMER2_INTERRUPT_ENABLE() |
||||||
|
#define TIMER2_INTERRUPT_DISABLE() |
||||||
|
#define ISR_NAME DMXinterrupt |
||||||
|
#define ISR(function, option) void function(void) |
||||||
|
#define BITS_PER_TIMER_TICK 500 |
||||||
|
void DMXinterrupt(void); |
||||||
|
IntervalTimer DMXtimer; |
||||||
|
|
||||||
|
#else |
||||||
|
#define TIMER2_INTERRUPT_ENABLE() |
||||||
|
#define TIMER2_INTERRUPT_DISABLE() |
||||||
|
#define ISR_NAME TIMER2_OVF_vect |
||||||
|
/* Produce an error (warnings to not print on Arduino's default settings)
|
||||||
|
*/ |
||||||
|
#error "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
|
||||||
|
#if defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) |
||||||
|
TCCR2A = (1 << WGM20); |
||||||
|
TCCR2B = (1 << CS22); // 64 prescaler
|
||||||
|
#elif defined(__AVR_ATmega32U4__) |
||||||
|
TCCR4B = (1 << CS42) | (1 << CS41) | (1 << CS40); |
||||||
|
#elif defined(CORE_TEENSY) && defined(__arm__) |
||||||
|
DMXtimer.begin(DMXinterrupt, 2000); |
||||||
|
#endif |
||||||
|
TIMER2_INTERRUPT_ENABLE(); |
||||||
|
} |
||||||
|
|
||||||
|
/** Stop the DMX engine
|
||||||
|
* Turns off the DMX interrupt routine |
||||||
|
*/ |
||||||
|
void dmxEnd() { |
||||||
|
TIMER2_INTERRUPT_DISABLE(); |
||||||
|
#if defined(CORE_TEENSY) && defined(__arm__) |
||||||
|
DMXtimer.end(); |
||||||
|
#endif |
||||||
|
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. |
||||||
|
*/ |
||||||
|
#if defined(__AVR__) |
||||||
|
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)); |
||||||
|
} |
||||||
|
#elif defined(__arm__) |
||||||
|
void dmxSendByte(uint8_t value) { |
||||||
|
uint32_t begin, target; |
||||||
|
uint8_t mask; |
||||||
|
|
||||||
|
noInterrupts(); |
||||||
|
ARM_DEMCR |= ARM_DEMCR_TRCENA; |
||||||
|
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; |
||||||
|
begin = ARM_DWT_CYCCNT; |
||||||
|
*dmxPort = 0; |
||||||
|
target = F_CPU / 250000; |
||||||
|
while (ARM_DWT_CYCCNT - begin < target) |
||||||
|
; // wait, start bit
|
||||||
|
for (mask = 1; mask; mask <<= 1) { |
||||||
|
*dmxPort = (value & mask) ? 1 : 0; |
||||||
|
target += (F_CPU / 250000); |
||||||
|
while (ARM_DWT_CYCCNT - begin < target) |
||||||
|
; // wait, data bits
|
||||||
|
} |
||||||
|
*dmxPort = 1; |
||||||
|
target += (F_CPU / 125000); |
||||||
|
while (ARM_DWT_CYCCNT - begin < target) |
||||||
|
; // wait, 2 stops bits
|
||||||
|
interrupts(); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
/** 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. |
||||||
|
*/ |
||||||
|
ISR(ISR_NAME, ISR_NOBLOCK) { |
||||||
|
|
||||||
|
// Prevent this interrupt running recursively
|
||||||
|
TIMER2_INTERRUPT_DISABLE(); |
||||||
|
|
||||||
|
uint16_t bitsLeft = BITS_PER_TIMER_TICK; // 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++) delayMicroseconds(8); |
||||||
|
*dmxPort |= dmxBit; |
||||||
|
delayMicroseconds(12); |
||||||
|
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(); |
||||||
|
} |
||||||
|
|
||||||
|
void dmxWrite(int channel, uint8_t value) { |
||||||
|
if (!dmxStarted) dmxBegin(); |
||||||
|
if ((channel > 0) && (channel <= DMX_SIZE)) { |
||||||
|
if (value < 0) value = 0; |
||||||
|
if (value > 255) value = 255; |
||||||
|
dmxMax = max((unsigned)channel, dmxMax); |
||||||
|
dmxBuffer[channel - 1] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void dmxMaxChannel(int channel) { |
||||||
|
if (channel <= 0) { |
||||||
|
// End DMX transmission
|
||||||
|
dmxEnd(); |
||||||
|
dmxMax = 0; |
||||||
|
} else { |
||||||
|
dmxMax = min(channel, DMX_SIZE); |
||||||
|
if (!dmxStarted) dmxBegin(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/* C++ wrapper */ |
||||||
|
|
||||||
|
|
||||||
|
/** Set output pin
|
||||||
|
* @param pin Output digital pin to use |
||||||
|
*/ |
||||||
|
void DmxSimplePlusClass::usePin(uint8_t pin) { |
||||||
|
bool restartRequired = dmxStarted; |
||||||
|
|
||||||
|
if (restartRequired) dmxEnd(); |
||||||
|
dmxPin = pin; |
||||||
|
if (restartRequired) dmxBegin(); |
||||||
|
} |
||||||
|
|
||||||
|
/** Set DMX maximum channel
|
||||||
|
* @param channel The highest DMX channel to use |
||||||
|
*/ |
||||||
|
void DmxSimplePlusClass::maxChannel(int channel) { |
||||||
|
dmxMaxChannel(channel); |
||||||
|
} |
||||||
|
|
||||||
|
/** Write to a DMX channel
|
||||||
|
* @param address DMX address in the range 1 - 512 |
||||||
|
*/ |
||||||
|
void DmxSimplePlusClass::write(int address, uint8_t value) { |
||||||
|
dmxWrite(address, value); |
||||||
|
} |
||||||
|
|
||||||
|
uint8_t DmxSimplePlusClass::read(int channel) { |
||||||
|
if (channel > 0 && channel <= dmxMax) |
||||||
|
return (dmxBuffer[channel]); |
||||||
|
else |
||||||
|
return (0); |
||||||
|
} |
||||||
|
DmxSimplePlusClass DmxSimple; |
@ -0,0 +1,24 @@ |
|||||||
|
/**
|
||||||
|
* DmxSimple - A simple interface to DMX. |
||||||
|
* |
||||||
|
* Copyright (c) 2008-2009 Peter Knight, Tinker.it! All rights reserved. |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
|
||||||
|
#if RAMEND <= 0x4FF |
||||||
|
#define DMX_SIZE 128 |
||||||
|
#else |
||||||
|
#define DMX_SIZE 512 |
||||||
|
#endif |
||||||
|
|
||||||
|
class DmxSimplePlusClass { |
||||||
|
public: |
||||||
|
void maxChannel(int); |
||||||
|
void write(int, uint8_t); |
||||||
|
void usePin(uint8_t); |
||||||
|
uint8_t read(int channel); |
||||||
|
}; |
||||||
|
extern DmxSimplePlusClass DmxSimplePlus; |
Loading…
Reference in new issue