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

author: Kalcifer
author_rank: Regular Contributor
timestamp: May 04, 2022, 12:33:37 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 */#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)volatile uint8_t number_of_received_bits = 0;volatile uint64_t raw_sensor_data = 0;volatile bool transmission_complete = true;uint8_t parsed_temperature;/*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_temperature(uint8_t* parsed_temperature);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);sensor_rx();uint8_t *temperature = parse_sensor_data(sensor_rx());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.PCMSK0 |= (1 << PCINT0); // Enable the interrupt for pin B1.// Set up the timer for use with the receive function:TCCR1B |= (1 << ICES1); // Set ICP1 to capture a falling edge.}// struct SensorData sensor_rx(void)uint64_t sensor_rx(void){raw_sensor_data = 0;number_of_received_bits = 0;transmission_complete = false;// 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) & ~0xFFFF0000;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){// 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){uint16_t bit_length = ICR1 * TIMER_COUNT_PERIOD;if (bit_length < 45) // Is a 0{// Do nothing. Doing nothing is the same as writing a zero.}else if (bit_length > 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.EDIT: I have moved this to the microcontrollers subforum.