SimpleDHT.cpp (10756B)
1 /* 2 The MIT License (MIT) 3 4 Copyright (c) 2016-2017 winlin 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 #include "SimpleDHT.h" 26 27 SimpleDHT::SimpleDHT() { 28 } 29 30 SimpleDHT::SimpleDHT(int pin) { 31 setPin(pin); 32 } 33 34 void SimpleDHT::setPin(int pin) { 35 this->pin = pin; 36 #ifdef __AVR 37 // (only AVR) - set low level properties for configured pin 38 bitmask = digitalPinToBitMask(pin); 39 port = digitalPinToPort(pin); 40 #endif 41 } 42 43 int SimpleDHT::setPinInputMode(uint8_t mode) { 44 if (mode != INPUT && mode != INPUT_PULLUP) { 45 return SimpleDHTErrPinMode; 46 } 47 this->pinInputMode = mode; 48 return SimpleDHTErrSuccess; 49 } 50 51 int SimpleDHT::read(byte* ptemperature, byte* phumidity, byte pdata[5]) { 52 int ret = SimpleDHTErrSuccess; 53 54 if (pin == -1) { 55 return SimpleDHTErrNoPin; 56 } 57 58 float temperature = 0; 59 float humidity = 0; 60 if ((ret = read2(&temperature, &humidity, pdata)) != SimpleDHTErrSuccess) { 61 return ret; 62 } 63 64 if (ptemperature) { 65 *ptemperature = static_cast<byte>(static_cast<int>(temperature)); 66 } 67 68 if (phumidity) { 69 *phumidity = static_cast<byte>(static_cast<int>(humidity)); 70 } 71 72 return ret; 73 } 74 75 int SimpleDHT::read(int pin, byte* ptemperature, byte* phumidity, byte pdata[5]) { 76 setPin(pin); 77 return read(ptemperature, phumidity, pdata); 78 } 79 80 #ifdef __AVR 81 int SimpleDHT::getBitmask() { 82 return bitmask; 83 } 84 85 int SimpleDHT::getPort() { 86 return port; 87 } 88 #endif 89 90 long SimpleDHT::levelTime(byte level, int firstWait, int interval) { 91 unsigned long time_start = micros(); 92 long time = 0; 93 94 #ifdef __AVR 95 uint8_t portState = level ? bitmask : 0; 96 #endif 97 98 bool loop = true; 99 for (int i = 0 ; loop; i++) { 100 if (time < 0 || time > levelTimeout) { 101 return -1; 102 } 103 104 if (i == 0) { 105 if (firstWait > 0) { 106 delayMicroseconds(firstWait); 107 } 108 } else if (interval > 0) { 109 delayMicroseconds(interval); 110 } 111 112 // for an unsigned int type, the difference have a correct value 113 // even if overflow, explanation here: 114 // https://arduino.stackexchange.com/questions/33572/arduino-countdown-without-using-delay 115 time = micros() - time_start; 116 117 #ifdef __AVR 118 loop = ((*portInputRegister(port) & bitmask) == portState); 119 #else 120 loop = (digitalRead(pin) == level); 121 #endif 122 } 123 124 return time; 125 } 126 127 //https://stackoverflow.com/a/2602885/4203189 128 byte SimpleDHT::reverse(byte b) { 129 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; 130 b = (b & 0xCC) >> 2 | (b & 0x33) << 2; 131 b = (b & 0xAA) >> 1 | (b & 0x55) << 1; 132 return b; 133 } 134 135 int SimpleDHT::parse(byte data[5], short* ptemperature, short* phumidity) { 136 short humidity = reverse(data[0]); 137 short humidity2 = reverse(data[1]); 138 short temperature = reverse(data[2]); 139 short temperature2 = reverse(data[3]); 140 byte check = reverse(data[4]); 141 byte expect = static_cast<byte>(humidity) + static_cast<byte>(humidity2) + static_cast<byte>(temperature) + static_cast<byte>(temperature2); 142 if (check != expect) { 143 return SimpleDHTErrDataChecksum; 144 } 145 *ptemperature = temperature<<8 | temperature2; 146 *phumidity = humidity<<8 | humidity2; 147 148 return SimpleDHTErrSuccess; 149 } 150 151 SimpleDHT11::SimpleDHT11() { 152 } 153 154 SimpleDHT11::SimpleDHT11(int pin) : SimpleDHT (pin) { 155 } 156 157 int SimpleDHT11::read2(float* ptemperature, float* phumidity, byte pdata[5]) { 158 int ret = SimpleDHTErrSuccess; 159 160 if (pin == -1) { 161 return SimpleDHTErrNoPin; 162 } 163 164 byte data[5] = {0}; 165 if ((ret = sample(data)) != SimpleDHTErrSuccess) { 166 return ret; 167 } 168 short temperature = 0; 169 short humidity = 0; 170 if ((ret = parse(data, &temperature, &humidity)) != SimpleDHTErrSuccess) { 171 return ret; 172 } 173 174 if (pdata) { 175 memcpy(pdata, data, 5); 176 } 177 if (ptemperature) { 178 *ptemperature = (int)(temperature>>8); 179 } 180 if (phumidity) { 181 *phumidity = (int)(humidity>>8); 182 } 183 184 // For example, when remove the data line, it will be success with zero data. 185 if (temperature == 0 && humidity == 0) { 186 return SimpleDHTErrZeroSamples; 187 } 188 189 return ret; 190 } 191 192 int SimpleDHT11::read2(int pin, float* ptemperature, float* phumidity, byte pdata[5]) { 193 setPin(pin); 194 return read2(ptemperature, phumidity, pdata); 195 } 196 197 int SimpleDHT11::sample(byte data[5]) { 198 // empty output data. 199 memset(data, 0, 5); 200 201 // According to protocol: [1] https://akizukidenshi.com/download/ds/aosong/DHT11.pdf 202 // notify DHT11 to start: 203 // 1. PULL LOW 20ms. 204 // 2. PULL HIGH 20-40us. 205 // 3. SET TO INPUT or INPUT_PULLUP. 206 // Changes in timing done according to: 207 // [2] https://www.mouser.com/ds/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf 208 // - original values specified in code 209 // - since they were not working (MCU-dependent timing?), replace in code with 210 // _working_ values based on measurements done with levelTimePrecise() 211 pinMode(pin, OUTPUT); 212 digitalWrite(pin, LOW); // 1. 213 delay(20); // specs [2]: 18us 214 215 // Pull high and set to input, before wait 40us. 216 // @see https://github.com/winlinvip/SimpleDHT/issues/4 217 // @see https://github.com/winlinvip/SimpleDHT/pull/5 218 digitalWrite(pin, HIGH); // 2. 219 pinMode(pin, this->pinInputMode); 220 delayMicroseconds(25); // specs [2]: 20-40us 221 222 // DHT11 starting: 223 // 1. PULL LOW 80us 224 // 2. PULL HIGH 80us 225 long t = levelTime(LOW); // 1. 226 if (t < 30) { // specs [2]: 80us 227 return simpleDHTCombileError(t, SimpleDHTErrStartLow); 228 } 229 230 t = levelTime(HIGH); // 2. 231 if (t < 50) { // specs [2]: 80us 232 return simpleDHTCombileError(t, SimpleDHTErrStartHigh); 233 } 234 235 // DHT11 data transmite: 236 // 1. 1bit start, PULL LOW 50us 237 // 2. PULL HIGH: 238 // - 26-28us, bit(0) 239 // - 70us, bit(1) 240 for (int j = 0; j < 40; j++) { 241 t = levelTime(LOW); // 1. 242 if (t < 24) { // specs says: 50us 243 return simpleDHTCombileError(t, SimpleDHTErrDataLow); 244 } 245 246 // read a bit 247 t = levelTime(HIGH); // 2. 248 if (t < 11) { // specs say: 20us 249 return simpleDHTCombileError(t, SimpleDHTErrDataRead); 250 } 251 bitWrite(data[j / 8], j % 8, (t > 40 ? 1 : 0)); // specs: 26-28us -> 0, 70us -> 1 252 } 253 254 // DHT11 EOF: 255 // 1. PULL LOW 50us. 256 t = levelTime(LOW); // 1. 257 if (t < 24) { // specs say: 50us 258 return simpleDHTCombileError(t, SimpleDHTErrDataEOF); 259 } 260 return SimpleDHTErrSuccess; 261 } 262 263 SimpleDHT22::SimpleDHT22() { 264 } 265 266 SimpleDHT22::SimpleDHT22(int pin) : SimpleDHT (pin) { 267 } 268 269 int SimpleDHT22::read2(float* ptemperature, float* phumidity, byte pdata[5]) { 270 int ret = SimpleDHTErrSuccess; 271 272 if (pin == -1) { 273 return SimpleDHTErrNoPin; 274 } 275 276 byte data[5] = {0}; 277 if ((ret = sample(data)) != SimpleDHTErrSuccess) { 278 return ret; 279 } 280 281 short temperature = 0; 282 short humidity = 0; 283 if ((ret = parse(data, &temperature, &humidity)) != SimpleDHTErrSuccess) { 284 return ret; 285 } 286 287 if (pdata) { 288 memcpy(pdata, data, 5); 289 } 290 if (ptemperature) { 291 *ptemperature = (float)((temperature & 0x8000 ? -1 : 1) * (temperature & 0x7FFF)) / 10.0; 292 } 293 if (phumidity) { 294 *phumidity = (float)humidity / 10.0; 295 } 296 297 return ret; 298 } 299 300 int SimpleDHT22::read2(int pin, float* ptemperature, float* phumidity, byte pdata[5]) { 301 setPin(pin); 302 return read2(ptemperature, phumidity, pdata); 303 } 304 305 int SimpleDHT22::sample(byte data[5]) { 306 // empty output data. 307 memset(data, 0, 5); 308 309 // According to protocol: http://akizukidenshi.com/download/ds/aosong/AM2302.pdf 310 // notify DHT22 to start: 311 // 1. T(be), PULL LOW 1ms(0.8-20ms). 312 // 2. T(go), PULL HIGH 30us(20-200us), use 40us. 313 // 3. SET TO INPUT or INPUT_PULLUP. 314 pinMode(pin, OUTPUT); 315 digitalWrite(pin, LOW); 316 delayMicroseconds(1000); 317 // Pull high and set to input, before wait 40us. 318 // @see https://github.com/winlinvip/SimpleDHT/issues/4 319 // @see https://github.com/winlinvip/SimpleDHT/pull/5 320 digitalWrite(pin, HIGH); 321 pinMode(pin, this->pinInputMode); 322 delayMicroseconds(5); 323 324 // DHT22 starting: 325 // 1. T(rel), PULL LOW 80us(75-85us). 326 // 2. T(reh), PULL HIGH 80us(75-85us). 327 long t = 0; 328 if ((t = levelTime(LOW)) < 30) { 329 return simpleDHTCombileError(t, SimpleDHTErrStartLow); 330 } 331 if ((t = levelTime(HIGH)) < 50) { 332 return simpleDHTCombileError(t, SimpleDHTErrStartHigh); 333 } 334 335 // DHT22 data transmite: 336 // 1. T(LOW), 1bit start, PULL LOW 50us(48-55us). 337 // 2. T(H0), PULL HIGH 26us(22-30us), bit(0) 338 // 3. T(H1), PULL HIGH 70us(68-75us), bit(1) 339 for (int j = 0; j < 40; j++) { 340 t = levelTime(LOW); // 1. 341 if (t < 24) { // specs says: 50us 342 return simpleDHTCombileError(t, SimpleDHTErrDataLow); 343 } 344 345 // read a bit 346 t = levelTime(HIGH); // 2. 347 if (t < 11) { // specs say: 26us 348 return simpleDHTCombileError(t, SimpleDHTErrDataRead); 349 } 350 bitWrite(data[j / 8], j % 8, (t > 40 ? 1 : 0)); // specs: 22-30us -> 0, 70us -> 1 351 } 352 353 // DHT22 EOF: 354 // 1. T(en), PULL LOW 50us(45-55us). 355 t = levelTime(LOW); 356 if (t < 24) { 357 return simpleDHTCombileError(t, SimpleDHTErrDataEOF); 358 } 359 360 return SimpleDHTErrSuccess; 361 }