[SOLVED] Help with trying to interface with a DHT22/AM2302 using an ATmega328P - Page 1
Forum section: electronics / general

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 12:46:09 am
content: I'm trying to read the temperature from a DHT22/AM2302 sensor using an ATmega328P and display it on a seven segment, but I am running into some issues. I'm thinking its my interrupts that are not working, but I'm not entirely sure what is going wrong exactly. I tried debugging the mcu using avr-gdb, and from what I can tell it's just simply not gathering any data from the sensor. I know that the sensor is working fine since I checked it on the scope: the mcu is requesting the data, and the sensor is transmitting it perfectly fine. The mcu is just not reading it.Here is my code:Code:/* System Defines */#define __AVR_ATmega328P__#define F_CPU 8000000UL#define INND_TS56/* AVR Includes */#include <avr/io.h>#include <avr/interrupt.h>#include <util/delay.h>/* GCC Includes */#include <stdbool.h>/* Custom Includes */#include "seven_segment.h"/* Program Defines */// TODO: Need to find a way to clean up the pins and ports with something like// this.#define SS_SEGMENTS_DIRECTION_PORT DDRD#define SS_SEGMENTS_OUTPUT_PORT PORTD#define SS_DISPLAY_ENABLE_DIRECTION_PORT DDRC#define SS_DISPLAY_ENABLE_OUTPUT_PORT PORTC#define SS_DISPLAY_0_ENABLE_PIN 0#define SS_DISPLAY_1_ENABLE_PIN 1#define SS_DISPLAY_2_ENABLE_PIN 2#define SS_DISPLAY_3_ENABLE_PIN 3#define SENSOR_DATA_DIRECTION_PORT DDRB#define SENSOR_DATA_OUTPUT_PORT PORTB#define SENSOR_DATA_INPUT_PORT PINB#define SENSOR_DATA_PIN 0#define TIMER_COUNT_PERIOD 0.0000001 // 1us per timer count (1/1MHz)// uint16_t received_temperature// NOTE: volatile because they are being accessed and modified by interrupts.volatile uint8_t number_of_received_bits = 0;volatile uint64_t raw_sensor_data = 0;volatile bool transmission_complete = true;// uint16_t temperature;uint8_t parsed_temperature;uint8_t rising_edge_counter = 0;uint8_t falling_edge_counter = 0;struct SensorData{uint16_t humidity;uint16_t temperature;uint8_t checksum_data;};void init(void);uint64_t sensor_rx(void);uint8_t* parse_sensor_data(uint64_t data);// void _display_digit(uint8_t digit);void display_temperature(uint8_t* parsed_temperature);// struct ReceivedData;void main(void){init();// The startup time of the sensor appears to be around 750us, but the// datasheet also says that it requires 2s between transmissions. The// following just prevents any data from being pulled from the sensor right// away after startup.SS_SEGMENTS_OUTPUT_PORT = 0xFF;SS_DISPLAY_ENABLE_OUTPUT_PORT |= (1 << SS_DISPLAY_0_ENABLE_PIN);_delay_ms(2000);uint8_t d = sensor_rx();if (d == 0){PORTB |= (1 << DDB7);}uint8_t *temperature = parse_sensor_data(d);display_temperature(temperature);while(1){}}void init(void){// Set all of the segment pins as outputs (they're all on port d)SS_SEGMENTS_DIRECTION_PORT = 0b11111111;// Set pins controlling the transistor multiplexers as outputs.// <display-2><display-1><display-0>// Enable Display 0SS_DISPLAY_ENABLE_DIRECTION_PORT |= (1 << SS_DISPLAY_0_ENABLE_PIN);// Enable Display 1SS_DISPLAY_ENABLE_DIRECTION_PORT |= (1 << SS_DISPLAY_1_ENABLE_PIN);// Enable Display 2SS_DISPLAY_ENABLE_DIRECTION_PORT |= (1 << SS_DISPLAY_2_ENABLE_PIN);// (Need a 4th display.)// Set up the interrupts:sei(); // Enable global interrupts.// PCICR |= (1 << PCIE0); // Enable the interrupts for PCINT[7:0].// NOTE: Perhaps only enable the below interrupt, the moment that you are// going to use it to prevent unwanted calls.PCMSK0 |= (1 << PCINT0); // Enable the interrupt for pin B1.// Set up the timer for use with the receive function:// TCCR1B |= (1 << CS11); // CLK/8 prescalerTCCR1B |= (1 << ICES1); // Set ICP1 to capture a falling edge.DDRB |= (1 << DDB7); // Test led}// struct SensorData sensor_rx(void)uint64_t sensor_rx(void){raw_sensor_data = 0;number_of_received_bits = 0;transmission_complete = false;// struct SensorData received_data;// Pull low 1ms minimum. This is done first so that the line isn't loaded// right away.SENSOR_DATA_OUTPUT_PORT &= ~(1 << SENSOR_DATA_PIN);// Set the output. Pulling the line low.SENSOR_DATA_DIRECTION_PORT |= (1 << SENSOR_DATA_PIN);_delay_ms(1); // Hold the line low for a minimum of 1ms.SENSOR_DATA_DIRECTION_PORT &=~ (1 << SENSOR_DATA_PIN); // Set as input._delay_us(80); // Wait 80us for the sensor to acknowledge.if (SENSOR_DATA_INPUT_PORT & SENSOR_DATA_PIN) // The sensor is not responding.{return 0; // Exit out of the function.}_delay_us(135); // Wait 135us to go into the midle of the start data transmission bit.PCICR |= (1 << PCIE0); // Enable the rising edge interrupt.while (!transmission_complete); // Wait here for the interrupt service routine to acquire data.return raw_sensor_data;}uint8_t* parse_sensor_data(uint64_t data){bool is_negative = false;uint16_t temperature_data = (data >> 8) & 0x0000FFFF;if (temperature_data & (1 << 15)){parsed_temperature = 1;}uint8_t temperature_digit_position = 0;while (temperature_digit_position <= 2){parsed_temperature = temperature_data % 10;temperature_data = temperature_data / 10;temperature_digit_position++;}return parsed_temperature;}void display_temperature(uint8_t* temperature){switch (temperature){case 0:SS_SEGMENTS_OUTPUT_PORT = SS_0;break;case 1:SS_SEGMENTS_OUTPUT_PORT = SS_1;break;case 2:SS_SEGMENTS_OUTPUT_PORT = SS_2;break;case 3:SS_SEGMENTS_OUTPUT_PORT = SS_3;break;case 4:SS_SEGMENTS_OUTPUT_PORT = SS_4;break;case 5:SS_SEGMENTS_OUTPUT_PORT = SS_5;break;case 6:SS_SEGMENTS_OUTPUT_PORT = SS_6;break;case 7:SS_SEGMENTS_OUTPUT_PORT = SS_7;break;case 8:SS_SEGMENTS_OUTPUT_PORT = SS_8;break;case 9:SS_SEGMENTS_OUTPUT_PORT = SS_9;break;}}// Interrupt Service routine for the start of a data bitISR (PCINT0_vect){rising_edge_counter++;// Interrupt flag is automatically cleared when the ISR is called.// Start the timer (starts at beginning of the bit)TCNT1 = 0; // Clear the timer.TCCR1B |= (1 << CS11); // Start the timer using CLK/8TIMSK1 |= (1 << ICIE1); // Enable the timer capture interrupt.PCICR &= ~(1 << PCIE0); // Disable the PCINT0 interrupt.}// Interrupt service routine for stopping the timer to catch the end of the bit.ISR (TIMER1_CAPT_vect){falling_edge_counter++;// float bit_length = ICR1 * TIMER_COUNT_PERIOD;if (ICR1 < 45) // Is a 0{// Do nothing. Doing nothing is the same as writing a zero.}else if (ICR1 > 45) // Is a 1{raw_sensor_data |= (1 << number_of_received_bits);}number_of_received_bits++; // Incrememnt the received bit counter.// Continue the capturing if 40 bits have not been acquired yet:if (number_of_received_bits < 40){TIMSK1 &= ~(1 << ICIE1); // Disable the input capture interrupt.PCICR |= (1 << PCIE0); // Enable the PCINT0 interrupt on pin B0}else{// Disable both interrupts:TIMSK1 &= ~(1 << ICIE1);PCICR &= ~(1 << PCIE0);transmission_complete = true;}}A quick summary of how the mcu receives from the sensor is I initially wait for the rising edge using the PCINT0 interrupt then when that triggers, I start a timer to time the length of a bit. I then wait for a falling edge to stop the timer which triggers a TIMER1_CAPT interrupt since PCINT0, and ICP1 are on the same pin (B1). From the length of the bit, I then determine whether or not the received bit is a 0 or a 1. I do that for each bit.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 02:51:20 am
content: I have verified that both of the interrupts are being called, and they are both being called the expected number of times. But for some reason the acquired data is absolute garbage.

author: ledtester
author_rank: Super Contributor
timestamp: May 04, 2022, 04:07:13 am
content: How about this approach... store the raw ICR1 values in an array so you can inspect them later. Their values might give you some insight into what the problem is.

author: ledtester
author_rank: Super Contributor
timestamp: May 04, 2022, 04:23:39 am
content: Quote from: ledtester on May 04, 2022, 04:07:13 amAnother idea is to simplify the interaction with the hardware by not setting TCNT1 = 0 but just let it run free. Then your captured ICR1 values will reflect when the interrupts were called. You can always subtract timestamps to get durations. For this to work I guess you'll also need to record TCNT1 at the start of each bit. The recorded timestamps might help in understanding what's going on.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 05:49:05 am
content: Quote from: ledtester on May 04, 2022, 04:23:39 amHalf of the solution has been found:I had an error in the TIMER1_CAPT vector. I was assembling the bits LSB to MSB when it should be MSB to LSB.So data is coming in now; however, there seems to be an issue with synchronization: I will get ~75% of the data in correct, and then all of a sudden the microcontroller will miss a bit, losing sync, so the rest of the data gets garbled. I'm not sure what's causing that. It's almost as if the interrupt is randomly not triggering correctly.

author: cv007
author_rank: Frequent Contributor
timestamp: May 04, 2022, 11:22:48 am
content: https://www.avrfreaks.net/forum/help-trying-interface-dht22am2302-using-atmega328p

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 07:41:41 pm
content: Quote from: cv007 on May 04, 2022, 11:22:48 amCorrect. Same post.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 08:10:58 pm
content: I created an array to record the the length (in us) of each bit that the microcontroller is seeing in case that would shed a light on any issues, and sure enough the timestamps were all over the map:{11, 11, 41, 15, 35, 35, 82, 40, 35, 83, 27, 35, 34, 35, 82, 88, 27, 35, 35, 34, 35, 34, 35, 48, 75, 75, 28, 35,83, 28, 35, 92, 27, 35, 35, 35, 83, 75, 75, 19}You can see from the picture below that they are only somewhat in the ballpark of the actual bit length, but are not at all reliable enough to get data from. I'm thinking that since there are bits that are both longer and shorter than what they should be, it probably has something to do with the falling edge interrupt not triggering properly. I enabled the input noise canceling bit for the capture unit, but that didn't seem to have any effect.It should be noted that I am using the internal RC oscillator as the clock source. I wouldn't think that it would vary so much in frequency to have this great of an effect... perhaps since I am trying to time things accurately, I should use a more accurate oscillator.ASIDE: I cannot figure out how to post an inline image on here.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 09:08:20 pm
content: I believe I have verified that it is not an issue with clock accuracy. I used an external function generator outputing a 1MHz square wave to clock the timer on the T1 signal pin. The recorded timestamps are still all over the place:{13, 13, 42, 16, 34, 35, 82, 40, 35, 35, 35, 34, 82, 27, 83, 40, 34, 35, 35, 35, 35, 35, 35, 47, 75, 75, 75, 27,35, 83, 28, 92, 76, 75, 75, 75, 28, 35, 35, 75}The lows should be in the mid 20us range, and the highs should be ~80us

author: ledtester
author_rank: Super Contributor
timestamp: May 04, 2022, 10:24:28 pm
content: Quote from: Kalcifer on May 04, 2022, 08:10:58 pmThat feature of the forum is broken right now, unfortunately.What timestamps do you get if you use a precise oscillator for both the system clock and the input signal - for instance, a precise 200 KHz clock signal? For now I would avoid using the RC oscillator.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 05, 2022, 02:57:37 am
content: Quote from: ledtester on May 04, 2022, 10:24:28 pmI did a test with an external function generator outputing a 1MHz square wave to clock the timer on the T1 signal pin. The recorded timestamps are still all over the place:{13, 13, 42, 16, 34, 35, 82, 40, 35, 35, 35, 34, 82, 27, 83, 40, 34, 35, 35, 35, 35, 35, 35, 47, 75, 75, 75, 27,35, 83, 28, 92, 76, 75, 75, 75, 28, 35, 35, 75}So it appears that the clock source is not the issue. This makes sense since the expected devation, calculated from the values given in the datasheet (+/- 10% RC oscillator calibration from the factory, and +/- 0.1MHz over a temperature range of 10C to 30C), for the transmitted 0 bit, for example, should only be min 23 to 29. As seen in the previously sent timestamps, the 0 bit goes as low as 11, and in this one with the function generator clocking the timer, 13.Not exactly sure what else is the issue.

author: ledtester
author_rank: Super Contributor
timestamp: May 05, 2022, 03:14:11 am
content: In looking at this discussion:https://forum.arduino.cc/t/question-about-icr1-of-timer-counter1/48299/3the problem was that the input capture flag was not being reset before enabling the input capture interrupt. Maybe that's an avenue to explore.Update: Here's some code to measure the period of an input signal using ICR1:https://electronics.stackexchange.com/a/175620/95488Update 2: from page 97 of the '328 datasheet:https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdfQuoteand another code example:https://gist.github.com/billroy/4032657

author: macboy
author_rank: Super Contributor
timestamp: May 05, 2022, 04:16:52 am
content: There is a simpler way to capture the bits. The bitstream consists of a low, about 50 us long, followed by a high, about 28 us long for a zero, or 70 us long for a one. As you are doing, a person could capture the actual duration of the high part and compare it to the expected duration, then make a decision about whether it's 0 or 1. The easier way is to simply sample the level at the midpoint, so at about 50 us after the data line goes high (use a pin change interrupt to start a timer). At 50 us, read the data line. If the bit was a 0 then it will already have transitioned to low (0), but if the bit is a 1 then it will still be high (1). So at this moment, the state of the data line IS the bit, you don't need to compare timestamps or anything. Then wait for the next low to high transition, and start the 50 us wait again. It's really simple and it works well. I'd likely use a timer 0 compare interrupt to get the ~50 us. Read the timer, add the offset, and write that to the compare register. This method is also more robust against an interrupt storm that could be caused by noise on the input, since you disable the edge interrupt during the wait time.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 05, 2022, 06:50:11 am
content: Quote from: ledtester on May 05, 2022, 03:14:11 amThat solved part of the issue. That along with another bug I found where i was actually setting the timer capture to capture on a rising edge instead of a falling edge (I was setting ICES1 whe it should be a 0 for a falling edge). However now I have a weird issue where the MCU is missing the first two bits, but the rest of the data is perfect, as seen in these recorded timestamps: {19, 21, 21, 21, 21, 68, 69, 69, 69, 21, 69, 69, 21, 20, 21, 21, 21, 21, 22, 21, 21, 20, 69, 69, 69, 69, 21, 69, 69, 20, 68, 69, 69, 21, 21, 21, 68, 68, 0, 0} The last two zeroes being that for some reason the microcontroller is only seeing 38 bits. it also only sees 38 falling edges and 39 rising edges. So something happens at the very beginning which causes it to miss some edges and ultimately the first two bits.Quote from: macboy on May 05, 2022, 04:16:52 amdefinitely! There's been many suggestions that I have received for simplifying, and optimizing the code, and I do plan on using those suggestions; however, I want to find the bugs in the code first and fix them. I learn by fixing mistakes.

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 06, 2022, 05:36:43 am
content: I have solved the final issue! Although, it seems like it was kind of an unfair one hahaThe issue turned out to be with the util/delay.h library. I'd been getting a warning in the compiler output saying that I should enable compiler optimizations because the library wouldn't operate properly without it. Well, I had been just ignoring that warning; the delay functions seemed to be working fine since macroscopically, when i looked on the scope they appeared to be creating the appropriate delays. However, when I looked more closely at the waveform I noticed that the delays were not EXACTLY as they were supposed to be. For example, the initial 1ms low signal that the microcontroller sends to trigger the sensor to send data was actually 1.053ms in length. When dealing with coars ms numbers, that 0.053ms isn't noticeable, but I am dealing with tens of us here which has a huge effect on reading the signal. So I figured that that warning might actually be worth paying attention to, and lo and behold, after enabling level 1 compiler optimizations with -O1, the delay was as expected, and the data was now being read completely perfect. Problem solved! The solution is somewhat aggavating, but I am glad that I got to the bottom of it.