Hi everyone,
I am having some issues reading from DHT11 connected to the digital port (30) in an Arduino Mega 2560 rev3 (clone). I don't want to use any library so I am trying to do everything using port manipulation. I have coded the following:
#include <avr/io.h>
#include <avr/iom2560.h>
#include "UART.h"
#include "timers.h"
#include "ioports.h"
int hum_temp[5][8];
void send_request()
{
//set_pin_mode();
DDRC |= _BV(7); //Set pin as output
PORTC &= 0x7F; //send low for 20 milliseconds;
wait_ms(20);
while (!sensor_comm);
PORTC |= _BV(7); //send high pulse for 40us
sensor_comm = 0;
wait_us(40);
while (!sensor_comm);
sensor_comm = 0;
DDRC &= 0x7F; //Set pin as input to wait a reply from the sensor
}
void receive_reply()
{
char * checkpoint3 = "checkpoint3\n";
char * checkpoint4 = "checkpoint4\n";
while ( !(PINC & _BV(7)) ) {send_UART(checkpoint3);} // ~54 us being low
while ( PINC & _BV(7) ) {send_UART(checkpoint4);} //80us being high
}
void receive_data()
{
int us_count = 0;
for (int i=0; i<5; i++)
{
for (int j=0; j<8; j++)
{
sensor_comm = 0;
wait_us(54);
while (!sensor_comm);
hum_temp[i][j]=0; //Clean bit
timer_2_us_count_sensor();
while (PINC & _BV(7)) //Check which bit is received 0 = ~24us as HIGH, 1 = ~70us as HIGH
{
if (sensor_count) {
us_count++;
sensor_count = 0;
}
while (!sensor_comm);
}
if (1 < us_count && us_count <= 4)
hum_temp[i][j] = 0;
else if (4 < us_count && us_count < 8)
hum_temp[i][j] = 1;
us_count = 0;
}
}
char * data = "data_received\n\n\n";
send_UART(data);
}
void wait_end_of_frame()
{
while ( !(PINC & _BV(7)) );
}
void read_temp_hum()
{
send_request();
receive_reply();
receive_data();
wait_end_of_frame();
}
The program gets into an endless loop in receive_reply(), in the checkpoint4.
Questions:
-
As I said, there are not libraries being used, so wait_ms and wait_us start timer3 and their interrupt sets sensor_comm=1. Is there any Nop() style commando to use in the empty Whiles. It gets stuck endlessly in them if its {} is empty. Therefore I use send_UART (deleted for cleaning purposes).
-
Is the receive_data function correct. I guess I have to diss out some values first (as said in the documentation), but appart from that? Same methodology is used here, timer_2_us_count_sensor() starts a timer which interrupts every us and sets the value sensor_count==1.
Thank you in advance and have a nice day!
Lizarduino
If you don't to use the Library, you have to convert the timing diagrams of DHT11 (as given in the data sheets) into C/C++/Arduino Codes.
DHT11.pdf (466 KB)
Hi again, thanks for answering to both of you.
I seem to have done the trick and everything works quite well. I need to polish it.
Hardware and setup used:
- Arduino Mega 2560 R3
- I am using port 30 -> PORTD & _BV(7)
- Custom delays, should be the same for standard delay functions.
- Custom serial (send_UART) function.
Important notes, common errors:
- There can't be any prints/serial_send in the code. This costs clock_cycles, the protocol is very strict with timing. Debuggin must be done in different fases. Check to a checkpoint, if it works, delete the chekpoint print and keep debugging.
- While loops + NOP() functions must be avoided. The reason is the same as before, NOP = one cycle = 62.5us. Taking into account the timings of the protocol, this can be decisive and break everything.
- 5 sec must pass between readings.
- Reading can be quite variable, this is a DHT11 problem (can't confirm, but it's what I have read and tested).
- Before starting any of port manipulation "projects", check if the hardware is working with .ino libraries.
DHT11.c
#define DHT11_PIN 7 // define Port 30
int read_dht11_byte()
{
int result=0;
int delay=50;
for (int i=0; i<8; i++)
{
delay_us(delay); //wait till 50us LOW is over.
delay_us(40); // Wait 40us since the actual bit is sent, and check its value
if ( (PINC & _BV(DHT11_PIN)) ) //if the signal is HIGH, the bit is 1. Else bit value is 0
{
result |= 1<<(7-i);
delay_us(30); //Wait until 70us in total has past.
delay = 50; //Before every bit there is 50us signal as LOW, get the delay value set
}
else
delay=38; // The bit was 0, which consisted of 28us in total. We have waited 40 already, we are in the middle of the 50us LOW signal. 50+28 - 40 = 38
}
return result;
}
void hasi()
{
delay_ms(2000);
DDRC |= _BV(DHT11_PIN); // Set port 30 as OUTPUT
PORTC |= _BV(DHT11_PIN); // Set port value HIGH
}
int segi()
{
char * information;
int values[5];
char * error;
int dht11_in;
/* REQUEST DHT11 TO GET READY */
PORTC &= ~_BV(DHT11_PIN); // Set port 30 value LOW for 18ms
delay_ms(18);
PORTC |= _BV(DHT11_PIN); // Set port 30 value HIGH for 40us
delay_us(40);
/* WAIT DHT RESPONSE */
DDRC &= ~_BV(DHT11_PIN); // Set port 30 as INPUT
while (PINC & _BV(DHT11_PIN)) {__asm__("nop\n\t");}
dht11_in = (PINC & _BV(DHT11_PIN)); // Check if the sensor has responded HIGH and delay 80us
if(dht11_in)
{
error = "\n\n\nDHT seems NOT to have responded - ERROR\n\n\n";
send_UART(error);
return 0;
}
delay_us(80);
dht11_in = (PINC & _BV(DHT11_PIN)); // Check if the sensor has switched port value to LOW -> if it did, the protocol is working
if(!dht11_in)
{
error = "\n\n\nDHT didn't pull HIGH - ERROR\n\n\n";
send_UART(error);
return 0;
}
delay_us(80);
/* Data transfer is about to start: 5 bytes are going to be read */
for (int i=0; i<5; i++)
values[i] = read_dht11_byte();
/* print received values */
for (int i=0; i<5; i++)
{
char value_char[3];
if (i==2)
values[i] = values[i]*40/255; //Since the value of DHT11 range is 0-40 for temperature, adapt the byte value received.
int_to_char(values[i], &value_char[0]);
send_UART(&value_char[0]);
char * space = "\n";
send_UART(space);
}
information = "Data transfer has ended\n";
send_UART(information);
return 1;
}
main.c
int main(void)
{
global_interrupts_disable();
setup_UART();
for (int j=0; j<10;j++)
{
delay_ms(3000);
delay_ms(2000);
init();
int reading = ask_for_values();
}
//reading = 1 OK
//reading = 0 ERROR
}
Hopefully this will be helpful for other people and will save them some time (a lot in my case).