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