Commit 7241451596ad427642eee4383443a263fa8919fb
Committed by
GitHub
1 parent
1e6536ea
Delete qqqDali.cpp
Showing
1 changed file
with
0 additions
and
436 deletions
qqqDali.cpp deleted
| 1 | -/*########################################################################### | |
| 2 | - qqqDali.cpp - copyright qqqlab.com / github.com/qqqlab | |
| 3 | - | |
| 4 | - This program is free software: you can redistribute it and/or modify | |
| 5 | - it under the terms of the GNU General Public License as published by | |
| 6 | - the Free Software Foundation, either version 3 of the License, or | |
| 7 | - (at your option) any later version. | |
| 8 | - | |
| 9 | - This program is distributed in the hope that it will be useful, | |
| 10 | - but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | - GNU General Public License for more details. | |
| 13 | - | |
| 14 | - You should have received a copy of the GNU General Public License | |
| 15 | - along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| 16 | - | |
| 17 | ----------------------------------------------------------------------------- | |
| 18 | -Changelog: | |
| 19 | -2020-11-10 Split off hardware specific code into separate class | |
| 20 | -2020-11-08 Created & tested on ATMega328 @ 8Mhz | |
| 21 | -###########################################################################*/ | |
| 22 | -#include "qqqDali.h" | |
| 23 | - | |
| 24 | -//########################################################################### | |
| 25 | -// Helpers | |
| 26 | -//########################################################################### | |
| 27 | -#define DALI_TOL 25 //percentage tolerance on timing. DALI specs call for 10%, but use higher value to allow for implementation micros() jitter. NOTE: Max value is 50% to differentiate between TE and 2TE. | |
| 28 | -#define DALI_TE ((1000000+(DALI_BAUD))/(2*(DALI_BAUD))) //417us | |
| 29 | -#define DALI_TE_MIN ((100-DALI_TOL)*DALI_TE)/100 | |
| 30 | -#define DALI_TE_MAX ((100+DALI_TOL)*DALI_TE)/100 | |
| 31 | -#define DALI_IS_TE(x) ((DALI_TE_MIN)<=(x) && (x)<=(DALI_TE_MAX)) | |
| 32 | -#define DALI_IS_2TE(x) ((2*(DALI_TE_MIN))<=(x) && (x)<=(2*(DALI_TE_MAX))) | |
| 33 | - | |
| 34 | -//########################################################################### | |
| 35 | -// Transmitter ISR | |
| 36 | -//########################################################################### | |
| 37 | -//called by derived class every Te period (417us) | |
| 38 | -void Dali::ISR_timer() { | |
| 39 | - if(this->bus_idle_te_cnt<0xff) this->bus_idle_te_cnt++; | |
| 40 | - | |
| 41 | - //send starbit, message bytes, 2 stop bits. | |
| 42 | - switch(this->tx_state) { | |
| 43 | - case TX_IDLE: | |
| 44 | - break; | |
| 45 | - case TX_START: | |
| 46 | - //wait for timeslot, then send start bit | |
| 47 | - if(this->bus_idle_te_cnt >= 22) { | |
| 48 | - this->HAL_set_bus_low(); | |
| 49 | - this->tx_bus_low=1; | |
| 50 | - this->tx_state = TX_START_X; | |
| 51 | - } | |
| 52 | - break; | |
| 53 | - case TX_START_X: | |
| 54 | - this->HAL_set_bus_high(); | |
| 55 | - this->tx_bus_low=0; | |
| 56 | - this->tx_pos=0; | |
| 57 | - this->tx_state = TX_BIT; | |
| 58 | - break; | |
| 59 | - case TX_BIT: | |
| 60 | - if(this->tx_msg[this->tx_pos>>3] & 1<<(7-(this->tx_pos&0x7))) {this->HAL_set_bus_low();this->tx_bus_low=1;} else {this->HAL_set_bus_high();this->tx_bus_low=0;} | |
| 61 | - this->tx_state = TX_BIT_X; | |
| 62 | - break; | |
| 63 | - case TX_BIT_X: | |
| 64 | - if(this->tx_msg[this->tx_pos>>3] & 1<<(7-(this->tx_pos&0x7))) {this->HAL_set_bus_high();this->tx_bus_low=0;} else {this->HAL_set_bus_low();this->tx_bus_low=1;} | |
| 65 | - this->tx_pos++; | |
| 66 | - if(this->tx_pos < this->tx_len) {this->tx_state = TX_BIT;} else {this->tx_state = TX_STOP1;} | |
| 67 | - break; | |
| 68 | - case TX_STOP1: | |
| 69 | - this->HAL_set_bus_high(); | |
| 70 | - this->tx_bus_low=0; | |
| 71 | - this->tx_state = TX_STOP1_X; | |
| 72 | - break; | |
| 73 | - case TX_STOP1_X: | |
| 74 | - this->tx_state = TX_STOP2; | |
| 75 | - break; | |
| 76 | - case TX_STOP2: | |
| 77 | - this->tx_state = TX_STOP2_X; | |
| 78 | - break; | |
| 79 | - case TX_STOP2_X: | |
| 80 | - this->tx_state = TX_STOP3; | |
| 81 | - break; | |
| 82 | - case TX_STOP3: | |
| 83 | - this->bus_idle_te_cnt=0; | |
| 84 | - this->tx_state = TX_IDLE; | |
| 85 | - this->rx_state = RX_IDLE; | |
| 86 | - this->rx_len = 0; | |
| 87 | - break; | |
| 88 | - } | |
| 89 | - | |
| 90 | - //handle receiver stop bits | |
| 91 | - if(this->rx_state == RX_BIT && this->bus_idle_te_cnt>4) { | |
| 92 | - this->rx_state = RX_IDLE; | |
| 93 | - //received two stop bits, got message in rx_msg + rx_halfbitlen | |
| 94 | - uint8_t bitlen = (this->rx_halfbitlen+1)>>1; | |
| 95 | - if((bitlen & 0x7) == 0) { | |
| 96 | - this->rx_len = bitlen>>3; | |
| 97 | - if(this->EventHandlerReceivedData) this->EventHandlerReceivedData(this, (uint8_t*)this->rx_msg, this->rx_len); | |
| 98 | - }else{ | |
| 99 | - //invalid bitlen | |
| 100 | - //TODO handle this | |
| 101 | - } | |
| 102 | - } | |
| 103 | -} | |
| 104 | - | |
| 105 | -//########################################################################### | |
| 106 | -// Receiver ISR | |
| 107 | -//########################################################################### | |
| 108 | -//called by derived class on bus state change | |
| 109 | -void Dali::ISR_pinchange() { | |
| 110 | - uint32_t ts = this->HAL_micros(); //get timestamp of change | |
| 111 | - this->bus_idle_te_cnt=0; //reset idle counter | |
| 112 | - uint8_t bus_low = this->HAL_is_bus_low(); | |
| 113 | - | |
| 114 | - //exit if transmitting | |
| 115 | - if(this->tx_state != TX_IDLE) { | |
| 116 | - //check tx collision | |
| 117 | - if(bus_low && !this->tx_bus_low) { | |
| 118 | - this->tx_state = TX_IDLE; //stop transmitter | |
| 119 | - this->tx_collision = 1; //mark collision | |
| 120 | - } | |
| 121 | - return; | |
| 122 | - } | |
| 123 | - | |
| 124 | - //no bus change, ignore | |
| 125 | - if(bus_low == this->rx_last_bus_low) return; | |
| 126 | - | |
| 127 | - //store values for next loop | |
| 128 | - uint32_t dt = ts - this->rx_last_change_ts; | |
| 129 | - this->rx_last_change_ts = ts; | |
| 130 | - this->rx_last_bus_low = bus_low; | |
| 131 | - | |
| 132 | - switch(this->rx_state) { | |
| 133 | - case RX_IDLE: | |
| 134 | - if(bus_low) { | |
| 135 | - this->rx_state = RX_START; | |
| 136 | - } | |
| 137 | - break; | |
| 138 | - case RX_START: | |
| 139 | - if(bus_low || !DALI_IS_TE(dt)) { | |
| 140 | - this->rx_state = RX_IDLE; | |
| 141 | - }else{ | |
| 142 | - this->rx_halfbitlen=-1; | |
| 143 | - for(uint8_t i=0;i<7;i++) this->rx_msg[0]=0; | |
| 144 | - this->rx_state = RX_BIT; | |
| 145 | - } | |
| 146 | - break; | |
| 147 | - case RX_BIT: | |
| 148 | - if(DALI_IS_TE(dt)) { | |
| 149 | - //got a single Te pulse | |
| 150 | - this->push_halfbit(bus_low); | |
| 151 | - } else if(DALI_IS_2TE(dt)) { | |
| 152 | - //got a double Te pulse | |
| 153 | - this->push_halfbit(bus_low); | |
| 154 | - this->push_halfbit(bus_low); | |
| 155 | - } else { | |
| 156 | - //got something else -> no good | |
| 157 | - this->rx_state = RX_IDLE; | |
| 158 | - //TODO rx error | |
| 159 | - return; | |
| 160 | - } | |
| 161 | - break; | |
| 162 | - } | |
| 163 | -} | |
| 164 | - | |
| 165 | -void Dali::push_halfbit(uint8_t bit) { | |
| 166 | - bit = (~bit)&1; | |
| 167 | - if((this->rx_halfbitlen & 1)==0) { | |
| 168 | - uint8_t i = this->rx_halfbitlen>>4; | |
| 169 | - if(i<3) { | |
| 170 | - this->rx_msg[i] = (this->rx_msg[i]<<1) | bit; | |
| 171 | - } | |
| 172 | - } | |
| 173 | - this->rx_halfbitlen++; | |
| 174 | -} | |
| 175 | - | |
| 176 | - | |
| 177 | -//########################################################################### | |
| 178 | -// Dali Class | |
| 179 | -//########################################################################### | |
| 180 | - | |
| 181 | -//non blocking send - check tx_state for completion, check tx_collision for collision errors | |
| 182 | -uint8_t Dali::send(uint8_t* tx_msg, uint8_t tx_len_bytes) { | |
| 183 | - if(tx_len_bytes>3) return -(DALI_RESULT_DATA_TOO_LONG); | |
| 184 | - if(this->tx_state != TX_IDLE) return -(DALI_RESULT_TIMEOUT); | |
| 185 | - for(uint8_t i=0;i<tx_len_bytes;i++) this->tx_msg[i]=tx_msg[i]; | |
| 186 | - this->tx_len = tx_len_bytes<<3; | |
| 187 | - this->tx_collision=0; | |
| 188 | - this->tx_state = TX_START; //start transmission | |
| 189 | - return 0; | |
| 190 | -} | |
| 191 | - | |
| 192 | -//blocking send - wait until successful send or timeout | |
| 193 | -uint8_t Dali::sendwait(uint8_t* tx_msg, uint8_t tx_len_bytes, uint32_t timeout_us) { | |
| 194 | - if(tx_len_bytes>3) return -(DALI_RESULT_DATA_TOO_LONG); | |
| 195 | - uint32_t ts = HAL_micros(); | |
| 196 | - | |
| 197 | - while(1) { | |
| 198 | - //wait for idle | |
| 199 | - while(this->tx_state != TX_IDLE) { | |
| 200 | - if(HAL_micros() - ts > timeout_us) return -(DALI_RESULT_TIMEOUT); | |
| 201 | - } | |
| 202 | - //start transmit | |
| 203 | - uint8_t rv = this->send(tx_msg,tx_len_bytes); | |
| 204 | - if(rv) return rv; | |
| 205 | - //wait for completion | |
| 206 | - while(this->tx_state != TX_IDLE) { | |
| 207 | - if(HAL_micros() - ts > timeout_us) return -(DALI_RESULT_TX_TIMEOUT); | |
| 208 | - } | |
| 209 | - //check for collisions | |
| 210 | - if(this->tx_collision==0) return 0; | |
| 211 | - } | |
| 212 | - return -(DALI_RESULT_TIMEOUT); | |
| 213 | -} | |
| 214 | - | |
| 215 | -//blocking transmit 2 byte command, receive 1 byte reply (if reply was sent) | |
| 216 | -int16_t Dali::tx(uint8_t cmd0, uint8_t cmd1, uint32_t timeout_us) { | |
| 217 | - uint8_t tx[2]; | |
| 218 | - tx[0] = cmd0; | |
| 219 | - tx[1] = cmd1; | |
| 220 | - int16_t rv = this->sendwait(tx, 2, timeout_us); | |
| 221 | - this->rx_halfbitlen = 0; | |
| 222 | - if(rv) return -rv;; | |
| 223 | - | |
| 224 | - //wait up to 10 ms for start of reply | |
| 225 | - uint32_t ts = HAL_micros(); | |
| 226 | - while(this->rx_state == RX_IDLE) { | |
| 227 | - if(HAL_micros() - ts > 10000) return DALI_RESULT_NO_REPLY; | |
| 228 | - } | |
| 229 | - //wait up to 15 ms for completion of reply | |
| 230 | - ts = HAL_micros(); | |
| 231 | - while(this->rx_len == 0) { | |
| 232 | - if(HAL_micros() - ts > 15000) return DALI_RESULT_NO_REPLY; | |
| 233 | - } | |
| 234 | - if(this->rx_len > 1) return DALI_RESULT_INVALID_REPLY; | |
| 235 | - return this->rx_msg[0]; | |
| 236 | -} | |
| 237 | - | |
| 238 | -//================================================================= | |
| 239 | -// High level | |
| 240 | -//================================================================= | |
| 241 | -//check YAAAAAA: 0000 0000 to 0011 1111 adr, 0100 0000 to 0100 1111 group, x111 1111 broadcast | |
| 242 | -uint8_t Dali::check_yaaaaaa(uint8_t yaaaaaa) { | |
| 243 | - return (yaaaaaa<=0b01001111 || yaaaaaa==0b01111111 || yaaaaaa==0b11111111); | |
| 244 | -} | |
| 245 | - | |
| 246 | -void Dali::set_level(uint8_t level, uint8_t adr) { | |
| 247 | - if(this->check_yaaaaaa(adr)) this->tx(adr<<1,level); | |
| 248 | -} | |
| 249 | - | |
| 250 | -int16_t Dali::cmd(uint16_t cmd, uint8_t arg) { | |
| 251 | - //Serial.print("dali_cmd[");Serial.print(cmd,HEX);Serial.print(",");Serial.print(arg,HEX);Serial.print(")"); | |
| 252 | - uint8_t cmd0,cmd1; | |
| 253 | - if(cmd&0x0100) { | |
| 254 | - //special commands: MUST NOT have YAAAAAAX pattern for cmd | |
| 255 | - //Serial.print(" SPC"); | |
| 256 | - if(!this->check_yaaaaaa(cmd>>1)) { | |
| 257 | - cmd0 = cmd; | |
| 258 | - cmd1 = arg; | |
| 259 | - }else{ | |
| 260 | - return DALI_RESULT_INVALID_CMD; | |
| 261 | - } | |
| 262 | - }else{ | |
| 263 | - //regular commands: MUST have YAAAAAA pattern for arg | |
| 264 | - | |
| 265 | - //Serial.print(" REG"); | |
| 266 | - if(this->check_yaaaaaa(arg)) { | |
| 267 | - cmd0 = arg<<1|1; | |
| 268 | - cmd1 = cmd; | |
| 269 | - }else{ | |
| 270 | - return DALI_RESULT_INVALID_CMD; | |
| 271 | - } | |
| 272 | - } | |
| 273 | - if(cmd&0x0200) { | |
| 274 | - //Serial.print(" REPEAT"); | |
| 275 | - this->tx(cmd0, cmd1); | |
| 276 | - } | |
| 277 | - int16_t rv = this->tx(cmd0, cmd1); | |
| 278 | - //Serial.print(" rv=");Serial.println(rv); | |
| 279 | - return rv; | |
| 280 | -} | |
| 281 | - | |
| 282 | - | |
| 283 | -uint8_t Dali::set_operating_mode(uint8_t v, uint8_t adr) { | |
| 284 | - return set_value(DALI_SET_OPERATING_MODE, DALI_QUERY_OPERATING_MODE, v, adr); | |
| 285 | -} | |
| 286 | - | |
| 287 | -uint8_t Dali::set_max_level(uint8_t v, uint8_t adr) { | |
| 288 | - return set_value(DALI_SET_MAX_LEVEL, DALI_QUERY_MAX_LEVEL, v, adr); | |
| 289 | -} | |
| 290 | - | |
| 291 | -uint8_t Dali::set_min_level(uint8_t v, uint8_t adr) { | |
| 292 | - return set_value(DALI_SET_MIN_LEVEL, DALI_QUERY_MIN_LEVEL, v, adr); | |
| 293 | -} | |
| 294 | - | |
| 295 | - | |
| 296 | -uint8_t Dali::set_system_failure_level(uint8_t v, uint8_t adr) { | |
| 297 | - return set_value(DALI_SET_SYSTEM_FAILURE_LEVEL, DALI_QUERY_SYSTEM_FAILURE_LEVEL, v, adr); | |
| 298 | -} | |
| 299 | - | |
| 300 | -uint8_t Dali::set_power_on_level(uint8_t v, uint8_t adr) { | |
| 301 | - return set_value(DALI_SET_POWER_ON_LEVEL, DALI_QUERY_POWER_ON_LEVEL, v, adr); | |
| 302 | -} | |
| 303 | - | |
| 304 | -//set a parameter value, returns 0 on success | |
| 305 | -uint8_t Dali::set_value(uint16_t setcmd, uint16_t getcmd, uint8_t v, uint8_t adr) { | |
| 306 | - int16_t current_v = this->cmd(getcmd,adr); //get current parameter value | |
| 307 | - if(current_v == v) return 0; | |
| 308 | - this->cmd(DALI_DATA_TRANSFER_REGISTER0,v); //store value in DTR | |
| 309 | - int16_t dtr = this->cmd(DALI_QUERY_CONTENT_DTR0,adr); //get DTR value | |
| 310 | - if(dtr != v) return 1; | |
| 311 | - this->cmd(setcmd,adr); //set parameter value = DTR | |
| 312 | - current_v = this->cmd(getcmd,adr); //get current parameter value | |
| 313 | - if(current_v != v) return 2; | |
| 314 | - return 0; | |
| 315 | -} | |
| 316 | - | |
| 317 | - | |
| 318 | -//====================================================================== | |
| 319 | -// Commissioning short addresses | |
| 320 | -//====================================================================== | |
| 321 | - | |
| 322 | -//Sets the slave Note 1 to the INITIALISE status for15 minutes. | |
| 323 | -//Commands 259 to 270 are enabled only for a slave in this | |
| 324 | -//status. | |
| 325 | - | |
| 326 | -//set search address | |
| 327 | -void Dali::set_searchaddr(uint32_t adr) { | |
| 328 | - this->cmd(DALI_SEARCHADDRH,adr>>16); | |
| 329 | - this->cmd(DALI_SEARCHADDRM,adr>>8); | |
| 330 | - this->cmd(DALI_SEARCHADDRL,adr); | |
| 331 | -} | |
| 332 | - | |
| 333 | -//set search address, but set only changed bytes (takes less time) | |
| 334 | -void Dali::set_searchaddr_diff(uint32_t adr_new,uint32_t adr_current) { | |
| 335 | - if( (uint8_t)(adr_new>>16) != (uint8_t)(adr_current>>16) ) this->cmd(DALI_SEARCHADDRH,adr_new>>16); | |
| 336 | - if( (uint8_t)(adr_new>>8) != (uint8_t)(adr_current>>8) ) this->cmd(DALI_SEARCHADDRM,adr_new>>8); | |
| 337 | - if( (uint8_t)(adr_new) != (uint8_t)(adr_current) ) this->cmd(DALI_SEARCHADDRL,adr_new); | |
| 338 | -} | |
| 339 | - | |
| 340 | -//Is the random address smaller or equal to the search address? | |
| 341 | -uint8_t Dali::compare() { | |
| 342 | - return (0xff == this->cmd(DALI_COMPARE,0x00)); | |
| 343 | -} | |
| 344 | - | |
| 345 | -//The slave shall store the received 6-bit address (AAAAAA) as a short address if it is selected. | |
| 346 | -void Dali::program_short_address(uint8_t shortadr) { | |
| 347 | - this->cmd(DALI_PROGRAM_SHORT_ADDRESS, (shortadr << 1) | 0x01); | |
| 348 | -} | |
| 349 | - | |
| 350 | -//What is the short address of the slave being selected? | |
| 351 | -uint8_t Dali::query_short_address() { | |
| 352 | - return this->cmd(DALI_QUERY_SHORT_ADDRESS, 0x00) >> 1; | |
| 353 | -} | |
| 354 | - | |
| 355 | -//find addr with binary search | |
| 356 | -uint32_t Dali::find_addr() { | |
| 357 | - uint32_t adr = 0x800000; | |
| 358 | - uint32_t addsub = 0x400000; | |
| 359 | - uint32_t adr_last = adr; | |
| 360 | - this->set_searchaddr(adr); | |
| 361 | - | |
| 362 | - while(addsub) { | |
| 363 | - this->set_searchaddr_diff(adr,adr_last); | |
| 364 | - adr_last = adr; | |
| 365 | - uint8_t cmp = this->compare(); | |
| 366 | - //Serial.print("cmp "); | |
| 367 | - //Serial.print(adr,HEX); | |
| 368 | - //Serial.print(" = "); | |
| 369 | - //Serial.println(cmp); | |
| 370 | - if(cmp) adr-=addsub; else adr+=addsub; | |
| 371 | - addsub >>= 1; | |
| 372 | - } | |
| 373 | - this->set_searchaddr_diff(adr,adr_last); | |
| 374 | - adr_last = adr; | |
| 375 | - if(!this->compare()) { | |
| 376 | - adr++; | |
| 377 | - this->set_searchaddr_diff(adr,adr_last); | |
| 378 | - } | |
| 379 | - return adr; | |
| 380 | -} | |
| 381 | - | |
| 382 | -//init_arg=11111111 : all without short address | |
| 383 | -//init_arg=00000000 : all | |
| 384 | -//init_arg=0AAAAAA1 : only for this shortadr | |
| 385 | -//returns number of new short addresses assigned | |
| 386 | -uint8_t Dali::commission(uint8_t init_arg) { | |
| 387 | - uint8_t cnt = 0; | |
| 388 | - uint8_t arr[64]; | |
| 389 | - uint8_t sa; | |
| 390 | - for(sa=0; sa<64; sa++) arr[sa]=0; | |
| 391 | - | |
| 392 | - //start commissioning | |
| 393 | - this->cmd(DALI_RESET,0x00); | |
| 394 | - this->cmd(DALI_INITIALISE,init_arg); | |
| 395 | - this->cmd(DALI_RANDOMISE,0x00); | |
| 396 | - | |
| 397 | - //find used short addresses (run always, seems to work better than without...) | |
| 398 | - for(sa = 0; sa<64; sa++) { | |
| 399 | - int16_t rv = this->cmd(DALI_QUERY_STATUS,sa); | |
| 400 | - if(rv!=DALI_RESULT_NO_REPLY) { | |
| 401 | - if(init_arg!=0b00000000) arr[sa]=1; //remove address from list if not in "all" mode | |
| 402 | - } | |
| 403 | - } | |
| 404 | - | |
| 405 | - //find random addresses and assign unused short addresses | |
| 406 | - while(1) { | |
| 407 | - uint32_t adr = this->find_addr(); | |
| 408 | - if(adr>0xffffff) break; //no more random addresses found -> exit | |
| 409 | - | |
| 410 | - //find first unused short address | |
| 411 | - for(sa=0; sa<64; sa++) { | |
| 412 | - if(arr[sa]==0) break; | |
| 413 | - } | |
| 414 | - if(sa>=64) break; //all 64 short addresses assigned -> exit | |
| 415 | - | |
| 416 | - //mark short address as used | |
| 417 | - arr[sa] = 1; | |
| 418 | - cnt++; | |
| 419 | - | |
| 420 | - //assign short address | |
| 421 | - this->program_short_address(sa); | |
| 422 | - | |
| 423 | - //Serial.println(this->query_short_address()); //TODO check read adr, handle if not the same... | |
| 424 | - | |
| 425 | - //remove the device from the search | |
| 426 | - this->cmd(DALI_WITHDRAW,0x00); | |
| 427 | - } | |
| 428 | - | |
| 429 | - //terminate the DALI_INITIALISE command | |
| 430 | - this->cmd(DALI_TERMINATE,0x00); | |
| 431 | - return cnt; | |
| 432 | -} | |
| 433 | - | |
| 434 | - | |
| 435 | - | |
| 436 | -//====================================================================== |