parent
6eaab7ea62
commit
1868592921
@ -1,293 +0,0 @@ |
|||||||
/**
|
|
||||||
* 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; |
|
@ -1,24 +0,0 @@ |
|||||||
/**
|
|
||||||
* 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