/*########################################################################### qqqDali.cpp - copyright qqqlab.com / github.com/qqqlab This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ---------------------------------------------------------------------------- Changelog: 2020-11-08 Created & tested on ATMega328 @ 8Mhz ###########################################################################*/ #include "qqqDali.h" #include "arduino.h" //########################################################################### // Helpers //########################################################################### #define DALI_BUS_LOW() digitalWrite(this->tx_pin,LOW); this->tx_bus_low=1 #define DALI_BUS_HIGH() digitalWrite(this->tx_pin,HIGH); this->tx_bus_low=0 #define DALI_IS_BUS_LOW() (digitalRead(this->rx_pin)==LOW) #define DALI_BAUD 1200 #define DALI_TE ((1000000+(DALI_BAUD))/(2*(DALI_BAUD))) //417us #define DALI_TE_MIN (80*DALI_TE)/100 #define DALI_TE_MAX (120*DALI_TE)/100 #define DALI_IS_TE(x) ((DALI_TE_MIN)<=(x) && (x)<=(DALI_TE_MAX)) #define DALI_IS_2TE(x) ((2*(DALI_TE_MIN))<=(x) && (x)<=(2*(DALI_TE_MAX))) //########################################################################### // Transmitter ISR //########################################################################### static Dali *IsrTimerHooks[DALI_HOOK_COUNT+1]; // timer compare interrupt service routine ISR(TIMER1_COMPA_vect) { for(uint8_t i=0;iISR_timer(); } } //called every Te period (417us) void Dali::ISR_timer() { if(this->bus_idle_te_cnt<0xff) this->bus_idle_te_cnt++; //send starbit, message bytes, 2 stop bits. switch(this->tx_state) { case TX_IDLE: break; case TX_START: //wait for timeslot, then send start bit if(this->bus_idle_te_cnt >= 22) { DALI_BUS_LOW(); this->tx_state = TX_START_X; } break; case TX_START_X: DALI_BUS_HIGH(); this->tx_pos=0; this->tx_state = TX_BIT; break; case TX_BIT: if(this->tx_msg[this->tx_pos>>3] & 1<<(7-(this->tx_pos&0x7))) {DALI_BUS_LOW();} else {DALI_BUS_HIGH();} this->tx_state = TX_BIT_X; break; case TX_BIT_X: if(this->tx_msg[this->tx_pos>>3] & 1<<(7-(this->tx_pos&0x7))) {DALI_BUS_HIGH();} else {DALI_BUS_LOW();} this->tx_pos++; if(this->tx_pos < this->tx_len) {this->tx_state = TX_BIT;} else {this->tx_state = TX_STOP1;} break; case TX_STOP1: DALI_BUS_HIGH(); this->tx_state = TX_STOP1_X; break; case TX_STOP1_X: this->tx_state = TX_STOP2; break; case TX_STOP2: this->tx_state = TX_STOP2_X; break; case TX_STOP2_X: this->tx_state = TX_STOP3; break; case TX_STOP3: this->bus_idle_te_cnt=0; this->tx_state = TX_IDLE; this->rx_state = RX_IDLE; this->rx_len = 0; break; } //handle receiver stop bits if(this->rx_state == RX_BIT && this->bus_idle_te_cnt>4) { this->rx_state = RX_IDLE; //received two stop bits, got message in rx_msg + rx_halfbitlen uint8_t bitlen = (this->rx_halfbitlen+1)>>1; if((bitlen & 0x7) == 0) { this->rx_len = bitlen>>3; if(this->EventHandlerReceivedData!=NULL) this->EventHandlerReceivedData(this, (uint8_t*)this->rx_msg, this->rx_len); }else{ //invalid bitlen //TODO handle this } } } //########################################################################### // Receiver ISR //########################################################################### //pin PCINT //0-7 PCINT2_vect PCINT16-23 //8-13 PCINT0_vect PCINT0-5 //14-19 PCINT1_vect PCINT8-13 static Dali *IsrPCINT0Hook; static Dali *IsrPCINT1Hook; static Dali *IsrPCINT2Hook; ISR(PCINT0_vect) { if(IsrPCINT0Hook!=NULL) IsrPCINT0Hook->ISR_pinchange(); } ISR(PCINT1_vect) { if(IsrPCINT1Hook!=NULL) IsrPCINT1Hook->ISR_pinchange(); } ISR(PCINT2_vect) { if(IsrPCINT2Hook!=NULL) IsrPCINT2Hook->ISR_pinchange(); } void Dali::ISR_pinchange() { uint32_t ts = micros(); //get timestamp of change this->bus_idle_te_cnt=0; //reset idle counter uint8_t bus_low = DALI_IS_BUS_LOW(); //exit if transmitting if(this->tx_state != TX_IDLE) { //check tx collision if(bus_low && !this->tx_bus_low) { this->tx_state = TX_IDLE; //stop transmitter this->tx_collision = 1; //mark collision } return; } //no bus change, ignore if(bus_low == this->rx_last_bus_low) return; //store values for next loop uint32_t dt = ts - this->rx_last_change_ts; this->rx_last_change_ts = ts; this->rx_last_bus_low = bus_low; switch(this->rx_state) { case RX_IDLE: if(bus_low) { this->rx_state = RX_START; } break; case RX_START: if(bus_low || !DALI_IS_TE(dt)) { this->rx_state = RX_IDLE; }else{ this->rx_halfbitlen=-1; for(uint8_t i=0;i<7;i++) this->rx_msg[0]=0; this->rx_state = RX_BIT; } break; case RX_BIT: if(DALI_IS_TE(dt)) { //got a single Te pulse this->push_halfbit(bus_low); } else if(DALI_IS_2TE(dt)) { //got a double Te pulse this->push_halfbit(bus_low); this->push_halfbit(bus_low); } else { //got something else -> no good this->rx_state = RX_IDLE; //TODO rx error return; } break; } } void Dali::push_halfbit(uint8_t bit) { bit = (~bit)&1; if((this->rx_halfbitlen & 1)==0) { uint8_t i = this->rx_halfbitlen>>4; if(i<3) { this->rx_msg[i] = (this->rx_msg[i]<<1) | bit; } } this->rx_halfbitlen++; } //########################################################################### // Dali Class //########################################################################### void Dali::begin(int8_t tx_pin, int8_t rx_pin) { this->tx_pin = tx_pin; this->rx_pin = rx_pin; this->tx_state = TX_IDLE; this->rx_state = RX_IDLE; //setup tx if(this->tx_pin>=0) { //setup tx pin pinMode(this->tx_pin, OUTPUT); DALI_BUS_HIGH(); //setup tx timer interrupt TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = (F_CPU+(DALI_BAUD))/(2*(DALI_BAUD)); // compare match register 16MHz/256/2Hz TCCR1B |= (1 << WGM12); // CTC mode TCCR1B |= (1 << CS10); // 1:1 prescaler TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt //setup timer interrupt hooks for(uint8_t i=0;irx_pin>=0) { //setup rx pin pinMode(this->rx_pin, INPUT); //setup rx pinchange interrupt // 0- 7 PCINT2_vect PCINT16-23 // 8-13 PCINT0_vect PCINT0-5 //14-19 PCINT1_vect PCINT8-13 if(this->rx_pin<=7){ PCICR |= (1<rx_pin)); IsrPCINT2Hook = this; //setup pinchange interrupt hook }else if(this->rx_pin<=13) { PCICR |= (1<rx_pin-8)); IsrPCINT0Hook = this; //setup pinchange interrupt hook }else if(this->rx_pin<=19) { PCICR |= (1<rx_pin-14)); IsrPCINT1Hook = this; //setup pinchange interrupt hook } } } uint8_t Dali::send(uint8_t* tx_msg, uint8_t tx_len_bytes) { if(tx_len_bytes>3) return -(DALI_RESULT_INVALID_TOO_LONG); if(this->tx_state != TX_IDLE) return -(DALI_RESULT_TIMEOUT); for(uint8_t i=0;itx_msg[i]=tx_msg[i]; this->tx_len = tx_len_bytes<<3; this->tx_collision=0; this->tx_state = TX_START; return 0; } uint8_t Dali::sendwait(uint8_t* tx_msg, uint8_t tx_len_bytes, uint32_t timeout_ms) { if(tx_len_bytes>3) return -(DALI_RESULT_INVALID_TOO_LONG); uint32_t ts = millis(); //wait for idle while(this->tx_state != TX_IDLE) { if(millis() - ts > timeout_ms) return -(DALI_RESULT_TIMEOUT); } //start transmit uint8_t rv = this->send(tx_msg,tx_len_bytes); if(rv) return rv; //wait for completion while(this->tx_state != TX_IDLE) { if(millis() - ts > timeout_ms) return -(DALI_RESULT_TX_TIMEOUT); } return 0; } //transmit 2 byte command, receive 1 byte reply int16_t Dali::tx(uint8_t cmd0, uint8_t cmd1, uint32_t timeout_ms) { uint8_t tx[2]; tx[0] = cmd0; tx[1] = cmd1; int16_t rv = this->sendwait(tx,2); this->rx_halfbitlen = 0; if(rv) return -rv;; //wait up to 10 ms for start of reply uint32_t ts = millis(); while(this->rx_state == RX_IDLE) { if(millis() - ts > 10) return DALI_RESULT_NO_REPLY; } //wait up to 15 ms for completion of reply ts = millis(); while(this->rx_len == 0) { if(millis() - ts > 15) return DALI_RESULT_NO_REPLY; } if(this->rx_len > 1) return DALI_RESULT_INVALID_REPLY; return this->rx_msg[0]; } //================================================================= // High level //================================================================= //check YAAAAAA: 0000 0000 to 0011 1111 adr, 0100 0000 to 0100 1111 group, x111 1111 broadcast uint8_t Dali::check_yaaaaaa(uint8_t yaaaaaa) { return (yaaaaaa<=0b01001111 || yaaaaaa==0b01111111 || yaaaaaa==0b11111111); } void Dali::set_level(uint8_t level, uint8_t adr) { if(this->check_yaaaaaa(adr)) this->tx(adr<<1,level); } int16_t Dali::cmd(uint16_t cmd, uint8_t arg) { //Serial.print("dali_cmd[");Serial.print(cmd,HEX);Serial.print(",");Serial.print(arg,HEX);Serial.print(")"); uint8_t cmd0,cmd1; if(cmd&0x0100) { //special commands: MUST NOT have YAAAAAAX pattern for cmd //Serial.print(" SPC"); if(!this->check_yaaaaaa(cmd>>1)) { cmd0 = cmd; cmd1 = arg; }else{ return DALI_RESULT_INVALID_CMD; } }else{ //regular commands: MUST have YAAAAAA pattern for arg //Serial.print(" REG"); if(this->check_yaaaaaa(arg)) { cmd0 = arg<<1|1; cmd1 = cmd; }else{ return DALI_RESULT_INVALID_CMD; } } if(cmd&0x0200) { //Serial.print(" REPEAT"); this->tx(cmd0, cmd1); } int16_t rv = this->tx(cmd0, cmd1); //Serial.print(" rv=");Serial.println(rv); return rv; } uint8_t Dali::set_operating_mode(uint8_t v, uint8_t adr) { return set_value(DALI_SET_OPERATING_MODE, DALI_QUERY_OPERATING_MODE, v, adr); } uint8_t Dali::set_max_level(uint8_t v, uint8_t adr) { return set_value(DALI_SET_MAX_LEVEL, DALI_QUERY_MAX_LEVEL, v, adr); } uint8_t Dali::set_min_level(uint8_t v, uint8_t adr) { return set_value(DALI_SET_MIN_LEVEL, DALI_QUERY_MIN_LEVEL, v, adr); } uint8_t Dali::set_system_failure_level(uint8_t v, uint8_t adr) { return set_value(DALI_SET_SYSTEM_FAILURE_LEVEL, DALI_QUERY_SYSTEM_FAILURE_LEVEL, v, adr); } uint8_t Dali::set_power_on_level(uint8_t v, uint8_t adr) { return set_value(DALI_SET_POWER_ON_LEVEL, DALI_QUERY_POWER_ON_LEVEL, v, adr); } //set a parameter value, returns 0 on success uint8_t Dali::set_value(uint16_t setcmd, uint16_t getcmd, uint8_t v, uint8_t adr) { int16_t current_v = this->cmd(getcmd,adr); //get current parameter value if(current_v == v) return 0; this->cmd(DALI_DATA_TRANSFER_REGISTER0,v); //store value in DTR int16_t dtr = this->cmd(DALI_QUERY_CONTENT_DTR0,adr); //get DTR value if(dtr != v) return 1; this->cmd(setcmd,adr); //set parameter value = DTR current_v = this->cmd(getcmd,adr); //get current parameter value if(current_v != v) return 2; return 0; } //====================================================================== // Commissioning short addresses //====================================================================== //Sets the slave Note 1 to the INITIALISE status for15 minutes. //Commands 259 to 270 are enabled only for a slave in this //status. //set search address void Dali::set_searchaddr(uint32_t adr) { this->cmd(DALI_SEARCHADDRH,adr>>16); this->cmd(DALI_SEARCHADDRM,adr>>8); this->cmd(DALI_SEARCHADDRL,adr); } //set search address, but set only changed bytes void Dali::set_searchaddr_diff(uint32_t adr_new,uint32_t adr_current) { if( (uint8_t)(adr_new>>16) != (uint8_t)(adr_current>>16) ) this->cmd(DALI_SEARCHADDRH,adr_new>>16); if( (uint8_t)(adr_new>>8) != (uint8_t)(adr_current>>8) ) this->cmd(DALI_SEARCHADDRM,adr_new>>8); if( (uint8_t)(adr_new) != (uint8_t)(adr_current) ) this->cmd(DALI_SEARCHADDRL,adr_new); } //Is the random address smaller or equal to the search address? uint8_t Dali::compare() { return (0xff == this->cmd(DALI_COMPARE,0x00)); } //The slave shall store the received 6-bit address (AAAAAA) as a short address if it is selected. void Dali::program_short_address(uint8_t shortadr) { this->cmd(DALI_PROGRAM_SHORT_ADDRESS, (shortadr << 1) | 0x01); } //What is the short address of the slave being selected? uint8_t Dali::query_short_address() { return this->cmd(DALI_QUERY_SHORT_ADDRESS, 0x00) >> 1; } //find addr with binary search uint32_t Dali::find_addr() { uint32_t adr = 0x800000; uint32_t addsub = 0x400000; uint32_t adr_last = adr; this->set_searchaddr(adr); while(addsub) { this->set_searchaddr_diff(adr,adr_last); adr_last = adr; uint8_t cmp = this->compare(); //Serial.print("cmp "); //Serial.print(adr,HEX); //Serial.print(" = "); //Serial.println(cmp); if(cmp) adr-=addsub; else adr+=addsub; addsub >>= 1; } this->set_searchaddr_diff(adr,adr_last); adr_last = adr; if(!this->compare()) { adr++; this->set_searchaddr_diff(adr,adr_last); } return adr; } //init_arg=11111111 : all without short address //init_arg=00000000 : all //init_arg=0AAAAAA1 : only for this shortadr uint8_t Dali::commission(uint8_t init_arg) { uint8_t cnt = 0; uint8_t arr[64]; uint8_t sa; for(sa=0; sa<64; sa++) arr[sa]=0; //find existing short addresses // if(init_arg==0xff) { Serial.println("Short adr"); for(sa = 0; sa<64; sa++) { int16_t rv = this->cmd(DALI_QUERY_STATUS,sa); if(rv!=DALI_RESULT_NO_REPLY) { arr[sa]=1; cnt++; Serial.print(sa); Serial.print(" status=0x"); Serial.print(rv,HEX); Serial.print(" minLevel="); Serial.println(this->cmd(DALI_QUERY_MIN_LEVEL,sa)); } } // } this->cmd(DALI_INITIALISE,init_arg); this->cmd(DALI_RANDOMISE,0x00); delay(100); while(cnt<64) { //Serial.print("addr="); //Serial.println(this->get_random_address(0xff),HEX); uint32_t adr = this->find_addr(); if(adr>0xffffff) break; Serial.print("found adr="); Serial.println(adr,HEX); //Serial.print("short adr="); //Serial.println(dali_query_short_address()); //find available address for(sa=0; sa<64; sa++) { if(arr[sa]==0) break; } if(sa>=64) break; arr[sa] = 1; cnt++; Serial.print("program short adr="); Serial.println(sa); this->program_short_address(sa); //dali_program_short_address(0xff); Serial.print("read short adr="); Serial.println(this->query_short_address()); this->cmd(DALI_WITHDRAW,0x00); } this->cmd(DALI_TERMINATE,0x00); return cnt; } //======================================================================