diff --git a/Dali.cpp b/Dali.cpp new file mode 100644 index 0000000..dfd4c6a --- /dev/null +++ b/Dali.cpp @@ -0,0 +1,400 @@ + +#include "Dali.h" +#include + + + +Dali::Dali() //constructor +{ + applyWorkAround1Mhz = 0; +} + + +void Dali::setTxPin(uint8_t pin) +{ + TxPin = pin; // user sets the digital pin as output + pinMode(TxPin, OUTPUT); + digitalWrite(TxPin, HIGH); +} + +void Dali::setRxAnalogPin(uint8_t pin) +{ + RxAnalogPin = pin; // user sets the digital pin as output +} + +void Dali::workAround1MhzTinyCore(uint8_t a) +{ + applyWorkAround1Mhz = a; +} + +void Dali::setupAnalogReceive(uint8_t pin) +{ + setRxAnalogPin(pin); // user sets the analog pin as input +} + + +void Dali::setupTransmit(uint8_t pin) +{ + setTxPin(pin); + speedFactor = 2; + //we don't use exact calculation of passed time spent outside of transmitter + //because of high ovehead associated with it, instead we use this + //emprirically determined values to compensate for the time loss + + #if F_CPU == 1000000UL + uint16_t compensationFactor = 88; //must be divisible by 8 for workaround + #elif F_CPU == 8000000UL + uint16_t compensationFactor = 12; + #else //16000000Mhz + uint16_t compensationFactor = 4; + #endif + +#if (F_CPU == 80000000UL) || (F_CPU == 160000000) // ESP8266 80MHz or 160 MHz + delay1 = delay2 = (HALF_BIT_INTERVAL >> speedFactor) - 2; +#else + delay1 = (HALF_BIT_INTERVAL >> speedFactor) - compensationFactor; + delay2 = (HALF_BIT_INTERVAL >> speedFactor) - 2; + + #if F_CPU == 1000000UL + delay2 -= 22; //22+2 = 24 is divisible by 8 + if (applyWorkAround1Mhz) { //definition of micro delay is broken for 1MHz speed in tiny cores as of now (May 2013) + //this is a workaround that will allow us to transmit on 1Mhz + //divide the wait time by 8 + delay1 >>= 3; + delay2 >>= 3; + } + #endif +#endif + + } + + +void Dali::transmit(uint8_t cmd1, uint8_t cmd2) // transmit commands to DALI bus (address byte, command byte) +{ + sendBit(1); + sendByte(cmd1); + sendByte(cmd2); + digitalWrite(TxPin, HIGH); +} + + +void Dali::sendByte(uint8_t b) +{ + for (int i = 7; i >= 0; i--) + { + sendBit((b >> i) & 1); + } +} + + +void Dali::sendBit(int b) +{ + if (b) { + sendOne(); + } + else { + sendZero(); + } +} + + +void Dali::sendZero(void) +{ + digitalWrite(TxPin, HIGH); + delayMicroseconds(delay2); + digitalWrite(TxPin, LOW); + delayMicroseconds(delay1); + +} + + +void Dali::sendOne(void) +{ + digitalWrite(TxPin, LOW); + delayMicroseconds(delay2); + digitalWrite(TxPin, HIGH); + delayMicroseconds(delay1); +} + + +void Dali::busTest() //DALI bus test +{ + int maxLevel; + int minLevel; + + //Luminaries must turn on and turn off. If not, check connection. + delay(100); + dali.transmit(BROADCAST_C, OFF_C); //Broadcast ON + delay(500); + dali.transmit(BROADCAST_C, ON_C); //Broadcast OFF + delay(100); + while (!Serial); + + //Receive response from luminaries: max and min level + dali.transmit(BROADCAST_C, QUERY_STATUS); + maxLevel = dali.maxResponseLevel(); + dali.transmit(BROADCAST_C, QUERY_STATUS); + minLevel = dali.minResponseLevel(); + + dali.analogLevel = (int)(maxLevel + minLevel) / 2; + + + + + +} + + +void Dali::splitAdd(long input, uint8_t &highbyte, uint8_t &middlebyte, uint8_t &lowbyte) +{ + highbyte = input >> 16; + middlebyte = input >> 8; + lowbyte = input; +} + + + +// define min response level +int Dali::minResponseLevel() +{ + + const uint8_t dalistep = 40; //us + uint16_t rxmin = 1024; + uint16_t dalidata; + long idalistep; + + + + for (idalistep = 0; idalistep < dali.daliTimeout; idalistep = idalistep + dalistep) { + dalidata = analogRead(RxAnalogPin); + if (dalidata < rxmin) { + rxmin = dalidata; + }; + delayMicroseconds(dalistep); + } + return rxmin; +} + +// define max response level +int Dali::maxResponseLevel() +{ + + const uint8_t dalistep = 40; //us + uint16_t rxmax = 0; + uint16_t dalidata; + long idalistep; + + + for (idalistep = 0; idalistep < dali.daliTimeout; idalistep = idalistep + dalistep) { + dalidata = analogRead(RxAnalogPin); + if (dalidata > rxmax) { + rxmax = dalidata; + }; + delayMicroseconds(dalistep); + } + return rxmax; +} + + +//scan for individual short address +void Dali::scanShortAdd() +{ + + const int delayTime = 10; + const uint8_t start_ind_adress = 0; + const uint8_t finish_ind_adress = 127; + uint8_t add_byte; + uint8_t device_short_add; + String response; + + dali.transmit(BROADCAST_C, OFF_C); // Broadcast Off + delay(delayTime); + Serial.println("Short addresses:"); + + for (device_short_add = start_ind_adress; device_short_add <= 63; device_short_add++) { + + add_byte = 1 + (device_short_add << 1); // convert short address to address byte + + + dali.transmit(add_byte, QUERY_STATUS); //query status + + + if (minResponseLevel() < dali.analogLevel) { + response = "YES"; + dali.transmit(add_byte, ON_C); // switch on + delay(1000); + dali.transmit(add_byte, OFF_C); // switch off + delay(1000); + } + else { + response = "NO"; + } + + + Serial.print("BIN: "); + Serial.print(device_short_add, BIN); + Serial.print(" "); + Serial.print("DEC: "); + Serial.print(device_short_add, DEC); + Serial.print(" "); + Serial.print("HEX: "); + Serial.print(device_short_add, HEX); + Serial.print(" "); + Serial.print("Response: "); + Serial.print(response); + Serial.println(); + + } + + dali.transmit(BROADCAST_C, ON_C); // Broadcast On + Serial.println(); + delay(delayTime); + +} + + +int Dali::readBinaryString(char *s) +{ + int result = 0; + while (*s) { + result <<= 1; + if (*s++ == '1') result |= 1; + } + return result; +} + + +bool Dali::cmdCheck(String & input, int & cmd1, int & cmd2) +{ + bool test = true; + + input.replace(" ", ""); // Delete spaces + + if (input.length() != 16) { + test = false; //check if command contain 16bit + } + else { + for (int i = 0; i <= input.length() - 1; i++) { + if ((int)input.charAt(i) == 49 or (int)input.charAt(i) == 48) {} + else { + test = false; + }; + }; + }; + + if (test) { + cmd1 = readBinaryString(input.substring(0, 8).c_str()); + cmd2 = readBinaryString(input.substring(8, 16).c_str()); + } + + return test; +} + +void Dali::initialisation() { + + const int delaytime = 10; //ms + + long low_longadd = 0x000000; + long high_longadd = 0xFFFFFF; + long longadd = (long)(low_longadd + high_longadd) / 2; + uint8_t highbyte; + uint8_t middlebyte; + uint8_t lowbyte; + uint8_t short_add = 0; + uint8_t cmd2; + + + delay(delaytime); + dali.transmit(BROADCAST_C, RESET); + delay(delaytime); + dali.transmit(BROADCAST_C, RESET); + delay(delaytime); + dali.transmit(BROADCAST_C, OFF_C); + delay(delaytime); + dali.transmit(0b10100101, 0b00000000); //initialise + delay(delaytime); + dali.transmit(0b10100101, 0b00000000); //initialise + delay(delaytime); + dali.transmit(0b10100111, 0b00000000); //randomise + delay(delaytime); + dali.transmit(0b10100111, 0b00000000); //randomise + + Serial.println("Searching fo long addresses:"); + + while (longadd <= 0xFFFFFF - 2 and short_add <= 64) { + while ((high_longadd - low_longadd) > 1) { + + dali.splitAdd(longadd, highbyte, middlebyte, lowbyte); //divide 24bit adress into three 8bit adresses + delay(delaytime); + dali.transmit(0b10110001, highbyte); //search HB + delay(delaytime); + dali.transmit(0b10110011, middlebyte); //search MB + delay(delaytime); + dali.transmit(0b10110101, lowbyte); //search LB + delay(delaytime); + dali.transmit(0b10101001, 0b00000000); //compare + + if (minResponseLevel() > dali.analogLevel) + { + low_longadd = longadd; + } + else + { + high_longadd = longadd; + } + + longadd = (low_longadd + high_longadd) / 2; //center + + Serial.print("BIN: "); + Serial.print(longadd + 1, BIN); + Serial.print(" "); + Serial.print("DEC: "); + Serial.print(longadd + 1, DEC); + Serial.print(" "); + Serial.print("HEX: "); + Serial.print(longadd + 1, HEX); + Serial.println(); + } // second while + + + if (high_longadd != 0xFFFFFF) + { + splitAdd(longadd + 1, highbyte, middlebyte, lowbyte); + dali.transmit(0b10110001, highbyte); //search HB + delay(delaytime); + dali.transmit(0b10110011, middlebyte); //search MB + delay(delaytime); + dali.transmit(0b10110101, lowbyte); //search LB + delay(delaytime); + dali.transmit(0b10110111, 1 + (short_add << 1)); //program short adress + delay(delaytime); + dali.transmit(0b10101011, 0b00000000); //withdraw + delay(delaytime); + dali.transmit(1 + (short_add << 1), ON_C); + delay(1000); + dali.transmit(1 + (short_add << 1), OFF_C); + delay(delaytime); + short_add++; + + Serial.println("Assigning a short address"); + + high_longadd = 0xFFFFFF; + longadd = (low_longadd + high_longadd) / 2; + + } + else { + Serial.println("End"); + } + } // first while + + + dali.transmit(0b10100001, 0b00000000); //terminate + dali.transmit(BROADCAST_C, ON_C); //broadcast on +} + + + + + + + Dali dali; diff --git a/Dali.h b/Dali.h new file mode 100644 index 0000000..64174c9 --- /dev/null +++ b/Dali.h @@ -0,0 +1,114 @@ + + +#ifndef dali_h +#define dali_h +#include + +//timer scaling factors for different transmission speeds +#define MAN_300 0 +#define MAN_600 1 +#define MAN_1200 2 +#define MAN_2400 3 +#define MAN_4800 4 +#define MAN_9600 5 +#define MAN_19200 6 +#define MAN_38400 7 + +/* +Timer 2 in the ATMega328 and Timer 1 in a ATtiny85 is used to find the time between +each transition coming from the demodulation circuit. +Their setup is for sampling the input in regular intervals. +For practical reasons we use power of 2 timer prescaller for sampling, +for best timing we use pulse lenght as integer multiple of sampling speed. +We chose to sample every 8 ticks, and pulse lenght of 48 ticks +thats 6 samples per pulse, lower sampling rate (3) will not work well for +innacurate clocks (like internal oscilator) higher sampling rate (12) will +cause too much overhead and will not work at higher transmission speeds. +This gives us 16000000Hz/48/256 = 1302 pulses per second (so it's not really 1200) +At different transmission speeds or on different microcontroller frequencies, clock prescaller is adjusted +to be compatible with those values. We allow about 50% clock speed difference both ways +allowing us to transmit even with up to 100% in clock speed difference +*/ + +// DALI coomands +#define BROADCAST_DP 0b11111110 +#define BROADCAST_C 0b11111111 +#define ON_DP 0b11111110 +#define OFF_DP 0b00000000 +#define ON_C 0b00000101 +#define OFF_C 0b00000000 +# define QUERY_STATUS 0b10010000 +# define RESET 0b00100000 + + +//setup timing for transmitter +#define HALF_BIT_INTERVAL 1666 + + + + + +#if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" + #include +#endif + +class Dali +{ + public: + Dali(); //the constructor + void setTxPin(uint8_t pin); //set the arduino digital pin for transmit. + void setRxAnalogPin(uint8_t pin); //set the arduino digital pin for receive. + void workAround1MhzTinyCore(uint8_t a = 1); //apply workaround for defect in tiny Core library for 1Mhz + void setupTransmit(uint8_t pin); //set up transmission + void setupAnalogReceive(uint8_t pin); + void transmit(uint8_t cmd1, uint8_t cmd2); //transmit 16 bits of data + void scanShortAdd(); //scan for short address + void busTest(); // bus test + void initialisation(); //initialization of new luminaries + bool cmdCheck(String & input, int & cmd1, int & cmd2); + + int minResponseLevel(); + int maxResponseLevel(); + + uint8_t speedFactor; + uint16_t delay1; + uint16_t delay2; + + long daliTimeout = 20000; //us, DALI response timeout + int analogLevel = 870; //analog border level (less - "0"; more - "1") + + + + + private: + + void sendByte(uint8_t b); //transmit 8 bits of data + void sendBit(int b); //transmit 1 bit of data + void sendZero(void); //transmit "0" + void sendOne(void); //transmit "1" + void splitAdd(long input, uint8_t &highbyte, uint8_t &middlebyte, uint8_t &lowbyte); //split random address + + + int readBinaryString(char *s); + + uint8_t TxPin; + uint8_t RxAnalogPin; + uint8_t applyWorkAround1Mhz; + uint8_t rxAnalogPin = 0; + +};//end of class Dali + +// Cant really do this as a real C++ class, since we need to have +// an ISR +extern "C" +{ + + + } + +extern Dali dali; + +#endif diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..5a197cc --- /dev/null +++ b/keywords.txt @@ -0,0 +1 @@ + dali KEYWORD1 setTxPin KEYWORD2 setRxAnalogPin KEYWORD2 workAround1MhzTinyCore KEYWORD2 setupTransmit KEYWORD2 setupAnalogReceive KEYWORD2 transmit KEYWORD2 scanShortAdd KEYWORD2 busTest KEYWORD2 initialisation KEYWORD2 cmdCheck KEYWORD2 minResponseLevel KEYWORD2 maxResponseLevel KEYWORD2 \ No newline at end of file