First working color changer.

main
Holger Wirtz 7 months ago
parent 6eaab7ea62
commit 1868592921
  1. 293
      DmxSimplePlus.cpp
  2. 24
      DmxSimplePlus.h
  3. 114
      RiTCh_Lightshow.ino

@ -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;

@ -14,7 +14,7 @@
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include "DmxSimplePlus.h"
#include "DmxSimple.h"
#include <EEPROM.h>
#include "looper.h"
@ -35,6 +35,11 @@
#define MAX_DMX_LEVEL 255
#define MAX_DMX_SPOTS 2
#define MIN_DMX_FADE_TIME 1
#define MAX_DMX_FADE_TIME 5
#define MIN_DMX_HOLD_TIME 3
#define MAX_DMX_HOLD_TIME 15
// Arduino pins
#define POTI1_PIN A1
#define POTI2_PIN A2
@ -55,19 +60,15 @@ uint32_t button_time[4] = { 0, 0, 0, 0 };
uint8_t poti_level[4] = { 0, 0, 0, 0 };
uint8_t brightness = LED_NORMAL_BRIGHTNESS;
enum {
DMX_RGB,
DMX_RGBW
};
typedef struct {
uint8_t dmx_type = DMX_RGB;
uint8_t dmx_type = 4;
uint16_t address = 0;
uint16_t steps = 0;
float diff[4] = { 0.0, 0.0, 0.0, 0.0 };
float values[4] = { 0.0, 0.0, 0.0, 0.0 };
} dmx_spot;
dmx_spot spot[2];
dmx_spot spot[MAX_DMX_SPOTS];
// schedular
looper sched;
@ -86,17 +87,21 @@ void button_check(void) {
uint16_t b = button(i);
if (b > BUTTON_LONG_PRESS && !bitRead(button_state, i)) {
// long press
// long press
#ifdef DEBUG
Serial.print(F("Button["));
Serial.print(i + 1, DEC);
Serial.println(F("]: long"));
#endif
button_time[i] = 0;
do_button_long(i);
} else if (b > MIN_TIME_SWITCH_PRESSED && !bitRead(button_state, i)) {
// short press
// short press
#ifdef DEBUG
Serial.print(F("Button["));
Serial.print(i + 1, DEC);
Serial.println(F("]: short"));
#endif
button_time[i] = 0;
do_button_short(i);
}
@ -111,10 +116,12 @@ void level_check(void) {
poti_lvl = map(analogRead(poti_pin_by_number(i)), 15, 1023, 0, 255);
if (poti_lvl != poti_level[i]) {
poti_level[i] = poti_lvl;
#ifdef DEBUG
Serial.print(F("Poti["));
Serial.print(i + 1, DEC);
Serial.print(F("]: "));
Serial.println(poti_level[i]);
#endif
do_level(i);
}
}
@ -126,32 +133,83 @@ void show_led(void) {
void worker(void) {
for (uint8_t s = 0; s < MAX_DMX_SPOTS; s++) {
uint8_t diff_counter = 0;
if (spot[s].steps == 0) {
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
if (spot[s].diff[i] != 0.0) {
diff_counter++;
spot[s].diff[i] = 0.0;
}
}
if (diff_counter != 0) {
// start hold timer
spot[s].steps = random(MIN_DMX_HOLD_TIME, MAX_DMX_HOLD_TIME) * 1000 / WORKER_SCHED;
#ifdef DEBUG
Serial.print(F("Spot "));
Serial.print(s, DEC);
Serial.print(F(" holding for "));
Serial.print(spot[s].steps * WORKER_SCHED / 1000, DEC);
Serial.println(F(" seconds"));
#endif
} else {
// new random values
spot[s].steps = random(MIN_DMX_FADE_TIME, MAX_DMX_FADE_TIME) * 1000 / WORKER_SCHED;
for (uint8_t i = 0; i < spot[s].dmx_type; i++)
spot[s].diff[i] = (random(0, 255) - spot[s].values[i]) / spot[s].steps;
#ifdef DEBUG
Serial.print(F("Spot "));
Serial.print(s, DEC);
Serial.print(F(" changing color for "));
Serial.print(spot[s].steps * WORKER_SCHED / 1000, DEC);
Serial.println(F(" seconds"));
#endif
}
} else {
for (uint8_t i = 0; i < 3; i++)
DmxSimplePlus.write(spot[s].address + i, uint8_t(float(DmxSimplePlus.read(spot[s].address + i)) + spot[s].diff[i] + 0.5));
for (uint8_t i = 0; i < spot[s].dmx_type; i++) {
spot[s].values[i] += spot[s].diff[i];
DmxSimple.write(spot[s].address + i, uint8_t(spot[s].values[i] + 0.5));
#ifdef DEBUG
Serial.print(F("Spot "));
Serial.print(s, DEC);
Serial.print(F(" color "));
Serial.print(i, DEC);
Serial.print(F(" step "));
Serial.print(spot[s].steps, DEC);
Serial.print(F(" diff "));
Serial.print(spot[s].diff[i], 4);
Serial.print(F(" value "));
Serial.println(spot[s].values[i], 4);
#endif
}
}
spot[s].steps--;
}
}
//--------------------------------------------------------------------------------
// HELPER FUNCTIONS
//--------------------------------------------------------------------------------
void do_level(uint8_t p) {
Serial.print("LEVEL ");
#ifdef DEBUG
Serial.print(F("LEVEL "));
Serial.println(p);
#endif
DmxSimplePlus.write(1 + p, poti_level[p]);
DmxSimple.write(1 + p, poti_level[p]);
}
void do_button_long(uint8_t b) {
Serial.print("LONG ");
#ifdef DEBUG
Serial.print(F("LONG "));
#endif
Serial.println(b);
}
void do_button_short(uint8_t b) {
Serial.print("SHORT ");
#ifdef DEBUG
Serial.print(F("SHORT "));
#endif
Serial.println(b);
}
@ -220,15 +278,18 @@ uint8_t poti_pin_by_number(byte n) {
// SYSTEM
//--------------------------------------------------------------------------------
void setup() {
#ifdef DEBUG
Serial.begin(9600);
#endif
#ifdef __AVR_ATmega32U4__
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
#endif
Serial.println("<setup begin>");
#ifdef DEBUG
Serial.println(F("<setup begin>"));
#endif
mySoftwareSerial.begin(9600);
@ -251,19 +312,24 @@ void setup() {
pinMode(LED_PIN, OUTPUT);
// setup DMX
DmxSimplePlus.usePin(DMX_PIN);
DmxSimplePlus.maxChannel(DMX_MAX_CHANNEL);
DmxSimple.usePin(DMX_PIN);
DmxSimple.maxChannel(DMX_MAX_CHANNEL);
// DMX setup
spot[0].address = 1;
spot[1].address = 5;
// setup audio card
// setup audio card
#ifdef DEBUG
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
#endif
for (uint8_t n = 0; n < 3; n++) {
if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3.
#ifdef DEBUG
Serial.print(F("Unable use DFPlayer: "));
Serial.println(n, DEC);
#endif
for (uint8_t i = 0; i < 3; i++) {
analogWrite(LED_PIN, LED_PLAY_BRIGHTNESS);
delay(100);
@ -271,7 +337,9 @@ void setup() {
delay(100);
}
} else {
#ifdef DEBUG
Serial.println(F("DFPlayer Mini online."));
#endif
myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms
break;
}
@ -284,7 +352,9 @@ void setup() {
sched.addJob(level_check, LEVEL_CHECK_SCHED);
sched.addJob(worker, WORKER_SCHED);
sched.addJob(show_led, LED_SCHED);
Serial.println("<setup end>");
#ifdef DEBUG
Serial.println(F("<setup end>"));
#endif
}
void loop() {

Loading…
Cancel
Save