You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
8.1 KiB
294 lines
8.1 KiB
8 months ago
|
/**
|
||
|
* 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;
|