RobTillaart / DHTNew

Arduino library for DHT11 and DHT22 with automatic sensor recognition
MIT License
98 stars 15 forks source link

read DHT11 with 3V3 logic/1Mhz #48

Closed betyar closed 3 years ago

betyar commented 3 years ago

Is it possible to somehow read a DHT11 with an ATMEGA328P running at 1 MHz? The reason the CPU is running so slow is because this is a battery operated project, thus I am trying to bring down current consumption to as low as possible. I tried using the minimum example and naturally received -999. I realize that the sensor is running too fast for the CPU at this rate, but is there some kind of trick to nevertheless capture the sensor readings?

RobTillaart commented 3 years ago

@betyar Not possible with one of the existing libraries. As the clock is 16x slower the minimum time micros can read is 64 us which would make it impossible to differentiate between a 0 and a 1 bit.

BUT there is always a but,

In itself it should be possible - thinking out loud

  1. take the core read function of DHTNew lib - it is stable @ 16MHz for some years now. Strip all non core code.
  2. choose a fixed pin for the sensor
  3. replace the digitalWrite / Reads with Direct Register functions to maximize performance (bitmasking )
  4. replace micros by a hardware timer to measure the time of the pulses in timer ticks. Let the timer tick at fastest speed, so at 1MHz it will increment +1 per usec. This should be accurate enough to measure the pulses reliably.
  5. might need some clever tricks for the math.

Another idea is to start from the example that does the pulse diagnose as it is pretty basic.

No beginner project but I expect it is doable in this way.

Q: Can you tell more about the project? Q: Why don't you use a DHT22? Q: Do you need to measure temp and humidity both? Q: What is your level of experience with hardware timers and registers etc ?

betyar commented 3 years ago

Many thanks Rob for your quick reply. The project I am working on is a small battery operated temperature and humidity sensor module that will take measurements once every half hour or hour and transmit the data via nRF24L01 for further processing. Currently I am planning on using it in small enclosed spaces such as a pantry or storage room, that is why it would be convenient if both temperature and humidity can be measured. For now I am using a DHT11 for prototyping and testing purposes since it is cheaper and still fairly accurate. After if it proves to be viable and works well, then I would use a DHT22 instead. Unfortunately my experience with hardware timers and registers is still at the beginner/intermediate stage, so I still have a lot to learn and try out. Your guidance and ideas are very helpful and I will look into them. Thanks for your help.

RobTillaart commented 3 years ago

I have no setup for 1 MHz but I created a first code example you can try. It works nearly perfect with a DHT22 at 16 MHz UNO

Please try it first at 16 MHZ and let me know if and how it works


In line 10 change #define MHZ to 1 2 4 8 16 In line 11 change #define TYPE to 11 or 22

//
//    FILE: DHT_1MHZ.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// PURPOSE: demo DHT reading with timer1.
//    DATE: 2021-01-11
//    (c) : MIT

const int MHZ  = 16;      // 8 4 2 1 ?
const int TYPE = 22;      // 11

//////////////////////////////////////
//
// TIMER 1 VAR
//
// overflow counter for timer
volatile uint32_t count = 0;

//////////////////////////////////////
//
// DHT11 SENSOR
//
int     datapin      = 10;
uint8_t bits[5];
float   temperature  = 0;
float   humidity     = 0;

//////////////////////////////////////
//
// SETUP & LOOP
//
void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  setupTimer1();
}

void loop()
{
  read(TYPE);
  Serial.print(temperature, 1);
  Serial.print('\t');
  Serial.println(humidity, 1);
  delay(2000);
}

////////////////////////////////////////////
//
// DHT11 core code - no error handling
//
bool read(uint8_t type)
{
  // EMPTY BITS BUFFER
  for (uint8_t i = 0; i < 5; i++) bits[i] = 0;

  // READ THE BYTES
  noInterrupts();
  int rv = readSensor(type);
  interrupts();
  if (rv != 0)
  {
    Serial.print("Error: ");
    Serial.println(rv);
  }
  //  debugging
  //  for (int i = 0; i < 5; i++)
  //  {
  //    Serial.print(bits[i], HEX);
  //    Serial.print(' ');
  //  }
  //  Serial.println();

  // Data-bus's free status is high voltage level.
  pinMode(datapin, OUTPUT);
  digitalWrite(datapin, HIGH);

  // DECODE TEMPERATURE AND HUMIDITY
  if (type == 22)
  {
    humidity    = (bits[0] * 256 + bits[1]) * 0.1;
    temperature = ((bits[2] & 0x7F) * 256 + bits[3]) * 0.1;
    if (bits[2] & 0x80)
    {
      temperature = -temperature;
    }
  }
  if (type == 11)
  {
    humidity    = bits[0] + bits[1] * 0.1;
    temperature = bits[2] + bits[3] * 0.1;
  }

  // TEST CHECKSUM
  uint8_t sum = bits[0] + bits[1] + bits[2] + bits[3];
  return (bits[4] != sum);
}

// LOW LEVEL CLOCKING IN BITS
int readSensor(uint8_t type)
{
  // REQUEST SAMPLE - SEND WAKEUP TO SENSOR
  pinMode(datapin, OUTPUT);
  digitalWrite(datapin, LOW);

  // WAIT  DHT22 1 ms    DHT11 18 ms
  uint32_t start = timer1Micros();
  if (type == 22) while (timer1Micros() - start < 1000);
  if (type == 11) while (timer1Micros() - start < 18000);

  // HOST GIVES CONTROL TO SENSOR
  digitalWrite(datapin, HIGH);
  if (MHZ == 1)
  {
    // delay 2 usec  1MHZ variant
    asm("nop");
    asm("nop");
  }
  else
  {
    delayMicroseconds(2);
  }
  pinMode(datapin, INPUT_PULLUP);

  // SENSOR PULLS LOW IF WAKEUP
  start = timer1Micros();
  while (digitalRead(datapin) == HIGH)
  {
    if (timer1Micros() - start > 20000) return -1;   // TIMEOUT
  }

  // SENSOR STAYS LOW for ~80 us
  start = timer1Micros();
  while (digitalRead(datapin) == LOW)
  {
    if (timer1Micros() - start > 100) return -2;     // TIMEOUT
  }

  // SENSOR STAYS HIGH for ~80 us
  start = timer1Micros();
  while (digitalRead(datapin) == HIGH)
  {
    if (timer1Micros() - start > 100) return -3;
  }

  // SENSOR HAS NOW SEND ACKNOWLEDGE ON WAKEUP
  // NOW IT SENDS THE BITS

  // READ THE OUTPUT - 40 BITS => 5 BYTES
  uint8_t mask = 0x80;
  uint8_t idx = 0;
  for (uint8_t i = 40; i != 0; i--)
  {
    while (digitalRead(datapin) == LOW);
    // DURATION OF HIGH DETERMINES 0 or 1
    // 26-28 us ==> 0
    //    70 us ==> 1
    start = timer1Micros();
    while (digitalRead(datapin) == HIGH);
    if (timer1Micros() - start > 50)
    {
      bits[idx] |= mask;
    }
    // PREPARE FOR NEXT BIT
    mask >>= 1;
    if (mask == 0)   // next byte?
    {
      mask = 0x80;
      idx++;
    }
  }
  return 0;
}

////////////////////////////////////////////
//
// TIMER1 timing
//
uint32_t timer1Micros()
{
  cli();
  uint16_t v = TCNT1;
  sei();
  return (count * 65536UL + v) / MHZ;
}

void setupTimer1()
{
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  TIMSK1 = (1 << TOIE1);    // Timer Overflow Interrupt Enable 1
  TCCR1B |= (1 << CS10);    // run at max speed
  sei();
}

ISR(TIMER1_OVF_vect)
{
  count++;
}

// -- END OF FILE --
RobTillaart commented 3 years ago

updated the code to catch the first bug :)

betyar commented 3 years ago

Many thanks for the code. I will try it tomorrow (it's late here; BTW Greetings from Hungary).

RobTillaart commented 3 years ago

OK good night from the Netherlands!

RobTillaart commented 3 years ago

I did a small measurement @ 16 MHz, - inserted at the end of setup()

  uint32_t start = timer1Micros();
  pinMode(10, OUTPUT);
  uint32_t dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  digitalWrite(10, HIGH);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  digitalWrite(10, LOW);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  int x = digitalRead(10);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

That means that these functions at 1 MHz take 16x as long so they are useless as the timing of the signals of the sensor is faster. So all these 3 functions should be rewritten in portmanipulation version.

https://www.arduino.cc/en/Reference/PortManipulation

To be continued.

RobTillaart commented 3 years ago

I got it working @ 16 MHz with timer1 and port manipulations.

This should work at lower clocks, maybe even at 1 MHz Still have some ideas to tweak it more if needed.

//
//    FILE: DHT_1MHZ.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// PURPOSE: demo DHT reading with timer1.
//    DATE: 2021-01-11
//    (c) : MIT

// to squeeze timing even more (and make it even more UNO specific)
// https://www.arduino.cc/en/Reference/PortManipulation

const int MHZ  = 16;      // 8 4 2 1 ?
const int TYPE = 22;      // 11

//////////////////////////////////////
//
// TIMER 1 VAR
//
// overflow counter for timer
volatile uint32_t count = 0;

//////////////////////////////////////
//
// DHT11 SENSOR
//
int     datapin      = 10;    // port B   mask = B00000100;  // 0x04;
uint8_t bits[5];
float   temperature  = 0;
float   humidity     = 0;

//////////////////////////////////////
//
// SETUP & LOOP
//
void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);

  setupTimer1();

  // measure basic functions

  uint32_t start = timer1Micros();
  pinMode(10, OUTPUT);
  uint32_t dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  digitalWrite(10, HIGH);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  digitalWrite(10, LOW);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

  start = timer1Micros();
  int x = digitalRead(10);
  dur = timer1Micros() - start;
  Serial.println(dur);
  delay(10);

}

void loop()
{
  read(TYPE);
  Serial.print(temperature, 1);
  Serial.print('\t');
  Serial.println(humidity, 1);
  delay(2000);
}

////////////////////////////////////////////
//
// DHT11 core code - no error handling
//
bool read(uint8_t type)
{
  // EMPTY BITS BUFFER
  for (uint8_t i = 0; i < 5; i++) bits[i] = 0;

  // READ THE BYTES
  noInterrupts();
  int rv = readSensor(type);
  interrupts();
  if (rv != 0)
  {
    Serial.print("Error: ");
    Serial.println(rv);
  }
  //  debugging
  //  for (int i = 0; i < 5; i++)
  //  {
  //    Serial.print(bits[i], HEX);
  //    Serial.print(' ');
  //  }
  //  Serial.println();

  // Data-bus's free status is high voltage level.
  // pinMode(datapin, OUTPUT);
  DDRB |= B00000100;
  // digitalWrite(datapin, HIGH);
  PORTB |= B00000100;

  // DECODE TEMPERATURE AND HUMIDITY
  if (type == 22)
  {
    humidity    = (bits[0] * 256 + bits[1]) * 0.1;
    temperature = ((bits[2] & 0x7F) * 256 + bits[3]) * 0.1;
    if (bits[2] & 0x80)
    {
      temperature = -temperature;
    }
  }
  if (type == 11)
  {
    humidity    = bits[0] + bits[1] * 0.1;
    temperature = bits[2] + bits[3] * 0.1;
  }

  // TEST CHECKSUM
  uint8_t sum = bits[0] + bits[1] + bits[2] + bits[3];
  return (bits[4] != sum);
}

// LOW LEVEL CLOCKING IN BITS
int readSensor(uint8_t type)
{
  // REQUEST SAMPLE - SEND WAKEUP TO SENSOR
  // pinMode(datapin, OUTPUT);
  DDRB |= B00000100;
  // digitalWrite(datapin, LOW);
  PORTB &= B11111011;

  // WAIT  DHT22 1 ms    DHT11 18 ms
  uint32_t start = timer1Micros();
  if (type == 22) while (timer1Micros() - start < 1000);
  if (type == 11) while (timer1Micros() - start < 18000);

  // HOST GIVES CONTROL TO SENSOR
  // digitalWrite(datapin, HIGH);
  PORTB |= B00000100;
  if (MHZ == 1)
  {
    // delay 2 usec  1MHZ variant
    asm("nop");
    asm("nop");
  }
  else
  {
    delayMicroseconds(2);
  }
  // pinMode(datapin, INPUT_PULLUP);
  DDRB = DDRB & B11111011;

  // SENSOR PULLS LOW IF WAKEUP
  start = timer1Micros();
  // while (digitalRead(datapin) == HIGH)
  while (PINB & B00000100)
  {
    if (timer1Micros() - start > 25000) return -1;   // TIMEOUT
  }

  // SENSOR STAYS LOW for ~80 us
  start = timer1Micros();
  // while (digitalRead(datapin) == LOW)
  while (!(PINB & B00000100))
  {
    if (timer1Micros() - start > 100) return -2;     // TIMEOUT
  }

  // SENSOR STAYS HIGH for ~80 us
  start = timer1Micros();
  // while (digitalRead(datapin) == HIGH)
  while (PINB & B00000100)
  {
    if (timer1Micros() - start > 100) return -3;
  }

  // SENSOR HAS NOW SEND ACKNOWLEDGE ON WAKEUP
  // NOW IT SENDS THE BITS

  // READ THE OUTPUT - 40 BITS => 5 BYTES
  uint8_t mask = 0x80;
  uint8_t idx = 0;
  for (uint8_t i = 40; i != 0; i--)
  {
    // while (digitalRead(datapin) == LOW);
    while (!(PINB & B00000100));
    // DURATION OF HIGH DETERMINES 0 or 1
    // 26-28 us ==> 0
    //    70 us ==> 1
    start = timer1Micros();
    // while (digitalRead(datapin) == HIGH);
    while (PINB & B00000100);
    if (timer1Micros() - start > 50)
    {
      bits[idx] |= mask;
    }
    // PREPARE FOR NEXT BIT
    mask >>= 1;
    if (mask == 0)   // next byte?
    {
      mask = 0x80;
      idx++;
    }
  }
  return 0;
}

////////////////////////////////////////////
//
// TIMER1 timing
//
uint32_t timer1Micros()
{
  cli();
  uint16_t v = TCNT1;
  sei();
  return (count * 65536UL + v) / MHZ;
}

void setupTimer1()
{
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  TIMSK1 = (1 << TOIE1);    // Timer Overflow Interrupt Enable 1
  TCCR1B |= (1 << CS10);    // run at max speed
  sei();
}

ISR(TIMER1_OVF_vect)
{
  count++;
}

// -- END OF FILE --
RobTillaart commented 3 years ago

Did you read - http://www.gammon.com.au/power by the way?

Another way to safe power is to make the VCC line of the DHT11 sensor under program control, (use a mosfet in between, do not directly feed the sensor from an IO pin.)

betyar commented 3 years ago

Did some tests. Some good news.

At 16 MHz works as expected. There are errors at the beginning (Error: -1 0 0) about 7 but then proceeds smoothly. Interestingly, when I use a mofset then there are always 10 errors at the beginning as opposed to the usual 7 without a mofset.

At 8 Mhz works the same as at 16 MHz. There are, however, a couple of additional errors after the usual ones at the beginning but they are few and far between.

At 1 Mhz it generally works. Errors are not reported but the transmission of the data is erratic. Here is the terminal result at 1 MHz:

16.1⸮5⸮.⸮⸮
16.1    49.⸮⸮⸮16.1⸮49.⸮⸮⸮16.2⸮49.⸮
16.2⸮49.⸮
⸮16.2   49.⸮⸮⸮16.1⸮49.⸮⸮⸮16.2⸮49.⸮⸮⸮16.2⸮49.⸮⸮⸮16.2⸮49.⸮⸮⸮16.2  48.⸮
16.2⸮48.⸮⸮
16.2⸮48.⸮
16.2    48.⸮
⸮16.2⸮48.⸮
16.2⸮48.⸮
16.2⸮48.⸮
⸮16.2⸮48.⸮⸮
16.2⸮48.⸮⸮
16.2⸮48.⸮⸮⸮16.2⸮48.⸮⸮
16.3⸮48.⸮⸮⸮16.2⸮48.⸮⸮⸮16.2  48.⸮⸮⸮16.3  48.⸮⸮⸮16.2⸮49.⸮⸮
16.2⸮48.⸮⸮⸮16.2⸮48.⸮⸮⸮16.3⸮48.⸮⸮
16.3⸮48.⸮
⸮16.4⸮48.⸮⸮⸮16.3⸮48.⸮⸮⸮16.3⸮48.⸮⸮
16.4    48.⸮⸮⸮16.3  48.⸮
⸮16.4⸮48.⸮⸮
16.4⸮48.⸮⸮⸮16.3⸮48.⸮⸮
16.4⸮48.⸮⸮
16.3    48.⸮
⸮16.3⸮47.⸮⸮⸮16.4⸮48.⸮
16.3⸮48.⸮⸮⸮16.4⸮47.⸮
⸮16.3⸮48.⸮⸮⸮16.3⸮47.⸮
16.4⸮48.⸮
16.3⸮47.⸮⸮⸮16.3⸮47.⸮⸮⸮16.4  4⸮.⸮⸮
16.3    48.⸮⸮⸮16.3  48.⸮
16.3⸮48.⸮
⸮16.4⸮47.⸮⸮⸮16.4⸮47.⸮
⸮16.4   47.⸮⸮⸮16.3⸮4⸮.⸮⸮⸮16.4   48.⸮⸮⸮16.4⸮47.⸮⸮⸮16.4⸮47.⸮
⸮16.3⸮4⸮.⸮⸮⸮16.3⸮47.⸮⸮⸮16.3⸮48.⸮⸮⸮16.3  48.⸮⸮⸮16.4⸮48.⸮⸮⸮16.3⸮47.⸮⸮⸮16.3⸮47.⸮⸮⸮16.3⸮47.⸮⸮⸮16.3⸮48.⸮⸮⸮16.3⸮48.⸮⸮
16.4⸮48.⸮
16.4⸮48.⸮
⸮16.4⸮47.⸮
16.3⸮47.⸮⸮⸮16.3⸮47.⸮⸮⸮16.3  48.⸮⸮⸮16.3⸮48.⸮
16.4⸮48.⸮⸮⸮16.4⸮47.⸮
⸮16.3⸮47.⸮⸮
16.4⸮47.⸮⸮
16.3    47.⸮⸮⸮16.3⸮48.⸮⸮
16.3    48.⸮
⸮

Generally you have proved that it can be done, I just now need to work a little on fine tuning. This is especially so in my case, because in my project the CPU goes to sleep for an extended period of time and then wakes up to take only a reading or two before it sleeps again. Ironically, the error message doesn't appear

betyar commented 3 years ago

By the way, the actual temperature and humidity readings are quite accurate for a DHT11.

RobTillaart commented 3 years ago

Great, good that it works. How do you handle baudrate?

I see special characters "⸮⸮⸮" popping up I assume these are \t an \n

RobTillaart commented 3 years ago

There are errors at the beginning (Error: -1 0 0) about 7 but then proceeds smoothly.

The sensor has a power up delay, that might be a few seconds. check datasheet

betyar commented 3 years ago

Sorry, had to pop put for a few minutes. I did not change anything in the code, the terminal baud rate was set to 115200. No doubt I will have to look into this as well as test it with the transceiver. For now I just did a simple test with the CPU, the sensor, and with/without the mofset. I will definitely check with the datasheet about the delay, I think it was 300 ms but I am not sure.

The hex for the special characters are the following: E2 B8 AE. They always followed in this order. The carriage return (0D) was not as frequent. Also at the very beginning was EF BB BF but that could be simply related to powering up.

RobTillaart commented 3 years ago

Quick math would say that 115200 baud would be 115200 / 16 = 7200 baud. Maybe a program like teraterm is better capable handling this unusual baudrate.

RobTillaart commented 3 years ago

The hex for the special characters are the following: E2 B8 AE. They always followed in this order. The carriage return (0D) was not as frequent. Also at the very beginning was EF BB BF but that could be simply related to powering up.

OK in powering up there might be some garbage (not too unusual)

What is expected is (excluding errors)

  Serial.print(temperature, 1);   //  digit digit point digit
  Serial.print('\t');             //  tab 
  Serial.println(humidity, 1);    //  digit digit point digit  newline

so EF BB BF are pretty unexpected...

betyar commented 3 years ago

Apologies once again for being late in replying. I have been trying out different scenarios and the most optimal I came up with was a baud rate of 19200; no errors and no garbage and the reading displayed accurately one line after the other. This was tested without a mofset. Now I will have the big task to try and integrate this with my code which includes a watchdog as well as the transmitter. Many thanks for your instructive help. I felt I learned quite a bit from it.

RobTillaart commented 3 years ago

No need to apologize, you probably have more things to do than only this project. Just like most of the people.

What should be possible with the baud rate is that you set it to 16x 19200 = 307200 The Arduino can send on any baudrate. As clocks are 16x slower this might work.

If the merging causes problems just let me know. Success!

betyar commented 3 years ago

Thanks again. I'll reopen if I have any further issues. All the best and keep up the great work. You're a great inspiration.

RobTillaart commented 3 years ago

Please let me know how things work in the end!