Trying to convert portmainpulation

I got this sensor: http://www.seeedstudio.com/depot/electronic-brick-indoor-temp-humidity-sensor-p-683.html
and this code for it:

#define DHT11_PIN 0 // ADC0

void setup()
{
  DDRC |= _BV(DHT11_PIN);
  PORTC |= _BV(DHT11_PIN);
  Serial.begin(9600); 
  Serial.println("Ready");
}

void loop()
{
  DHT11();
}
byte read_dht11_dat()
{
  byte i = 0;
  byte result=0;
  for(i=0; i< 8; i++){	
    while(!(PINC & _BV(DHT11_PIN))); // wait for 50us
    delayMicroseconds(30);	
    if(PINC & _BV(DHT11_PIN)) 
      result |=(1<<(7-i));
    while((PINC & _BV(DHT11_PIN))); // wait '1' finish


  }
  return result;
}


void DHT11()
{

  byte dht11_dat[5];
  byte dht11_in;
  byte i;
  // start condition
  // 1. pull-down i/o pin from 18ms
  PORTC &= ~_BV(DHT11_PIN);
  delay(18);
  PORTC |= _BV(DHT11_PIN);
  delayMicroseconds(40);

  DDRC &= ~_BV(DHT11_PIN);
  delayMicroseconds(40);

  dht11_in = PINC & _BV(DHT11_PIN);

  if(dht11_in){
    Serial.println("dht11 start condition 1 not met");
    return;
  }
  delayMicroseconds(80);

  dht11_in = PINC & _BV(DHT11_PIN);

  if(!dht11_in){
    Serial.println("dht11 start condition 2 not met");
    return;
  }
  delayMicroseconds(80);
  // now ready for data reception
  for (i=0; i<5; i++)
    dht11_dat[i] = read_dht11_dat();

  DDRC |= _BV(DHT11_PIN);
  PORTC |= _BV(DHT11_PIN);

  byte dht11_check_sum = dht11_dat[0]+dht11_dat[1]+dht11_dat[2]+dht11_dat[3];
  // check check_sum
  if(dht11_dat[4]!= dht11_check_sum)
  {
    Serial.println("DHT11 checksum error");
  }

  Serial.print("Current humdity = ");
  Serial.print(dht11_dat[0], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[1], DEC);
  Serial.print("% ");
  Serial.print("temperature = ");
  Serial.print(dht11_dat[2], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[3], DEC);
  Serial.println("C ");

  delay(200);
}

The code is working just fine, but my problem is that I don't understand it. It is a sample code provided by seeedstudio and I have only managed to get it to work on a normal arduino, not any mega. The problem there might be a different pin layout, but I am not really sure, I then tried to look at how I could change it, and then ran into things like DDRC |= _BV(DHT11_PIN); and PORTC |= _BV(DHT11_PIN); and found that it was some port manipulation going on.

My wish is now to be able to remove the port manipulation, and get stuff like analogRead and such in instead, things that I and other mortals might be better to understand. :slight_smile:

Anyone here that can help with this? Because the Arduino Reference - Arduino Reference page is still like Russian to me.

I'm guessing here, the gurus will no doubt set me straight, but my guess is:

DDRC |= _BV(DHT11_PIN);
DDRC &= ~_BV(DHT11_PIN);
PORTC |= _BV(DHT11_PIN);
PORTC &= ~_BV(DHT11_PIN);
dht11_in = PINC & _BV(DHT11_PIN);

is the same, or almost the same, (line for line) as:

pinMode (11, OUTPUT);
pinMode (11, INPUT);
digitalWrite (11, HIGH);  
digitalWrite (11, LOW); 
dht11_in = digitalRead (11);

Assuming all that 11 stuff refers to port 11 (or pin 11). Anyway, whatever port you plugged it into.

DDRC == The Port C Data Direction Register
PORTC == The Port C Data Register

The "real" digitalWrite is slightly safer (allowing for interrupts during the operation) and the "real" digitalRead returns HIGH or LOW (1 or 0) rather than non-zero or zero which your original code did. For your purposes there should be no problem substituting.

pinMode (DHT11_PIN, OUTPUT);
pinMode (DHT11_PIN, INPUT);
digitalWrite (DHT11_PIN, HIGH);  
digitalWrite (DHT11_PIN, LOW); 
dht11_in = digitalRead (DHT11_PIN);

Must be something like this then...

Just put it back in the cabinet where it controls the exhaust fan, so going to try this tomorrow. :slight_smile:

bld:

pinMode (DHT11_PIN, OUTPUT);

pinMode (DHT11_PIN, INPUT);
digitalWrite (DHT11_PIN, HIGH); 
digitalWrite (DHT11_PIN, LOW);
dht11_in = digitalRead (DHT11_PIN);



Must be something like this then...

Just put it back in the cabinet where it controls the exhaust fan, so going to try this tomorrow. :)

Only if it is on pin 0.

#define DHT11_PIN 0 // ADC0

You might be lucky, it looks like bit 0 of port C is in fact called pin 0, but that was a bit of a coincidence.

There code was referring to "bit 0 of port C" (where each port has 8 bits) not pin 0 on your board.

Looking at the schematic it looks like pin 23 of the Arduino which is Port C, bit 0, is in fact connected (on the Uno at least) to the pin marked A0. But that is really luck.

Assuming all that 11 stuff refers to port 11 (or pin 11).

This is not a safe assumption.

The Arduino pin assignments do not correlate to anything on the ATMega. Part of why the Arduino environment supports so many ATMega varients is because it maps the appropriate pin on the microcontroller to the pin on the Arduino board.

For hints to how Port pins correlate, read the PortManipulation document referenced in the OP's first post.

digitalWrite (11, HIGH);  
digitalWrite (11, LOW); 
dht11_in = digitalRead (11);
....

Obviously this is not the same as the original code. The original code includes a line that says "#define DHT11_PIN 0 // ADC0". None of the Arduino variants have a A/D pin on Pin 11.

You are right. But I regrouped in reply #4. I was trying to cover myself by saying "whatever port you plugged it into".

Looks like DHT11 refers to the device, a DHT11 sensor. So my guess about pin 11 was completely wrong.

Yeah, but that was the smallest issue, the biggest was to understand what all the other stuff were.

Yeah, but that was the smallest issue, the biggest was to understand what all the other stuff were.

Checkout this post on AVR Freaks. It helps explain what _bv() does:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=48246&start=0

I'm no expert, but here's my understanding of what's going on. Anyone that knows more than me, feel free to correct me so i can learn more.

/* 
Please refer to the pin to register mapping for whatever version of arduino you are using.
This works for ATMega168 or ATMega328
http://arduino.cc/playground/uploads/Learning/Atmega168PinMap2.png
This is for ATMega1280
http://forum.jeelabs.net/files/PIN%20MAPPING%20ARDUINO%20MEGA.jpg

For my comments below i will assume a ATMega168/328

You can also learn more about bitwise math at
http://194.81.104.27/~brian/microprocessor/BVMacro.pdf

*/



#define DHT11_PIN 0 // ADC0    
/*
Just to make it easier to follow, they have given a "friendly name" to the number 0 to be DHT11_PIN.  This means when you change 0 to another integer, it will change everywhere DHT11_PIN is mentioned.
*/

void setup()
{
  DDRC |= _BV(DHT11_PIN);
  /*  DDRC is the direction register (input or output)
      Same as saying
      DDRC = DDRC | _BV(DHT11_PIN);
        (or in words, DDRC equals bitwise OR of DDRC and the 8 bit binary representation of DHT11_PIN (which is 0, so the _BV(DHT11_PIN) is 00000000))
      The bits that DDRC is 1 (as opposed to 0) indicate an output
      The binary value of DDRC is represented by 8 bits (00000000 through 11111111).  I find that the binary version is easiest to read, as it's Register Pin 7 through 0 (left to right).
      Doing a bitwise OR will only make a pin go to 1 if what you are "ORing" it with has a 1, else anything else will remain the same as it originally was.
      for example:
                    10111100
                 OR 00001110
            Equals  10111110
      This is VERY important to understand the concept of.  If you don't understand bitwise math, brush up on it before reading any more on this.
      In this code, DHT11_PIN is 0, so:
            Whatever DDRC is, leave everything the same except for the last digit of the 8 bit binary representation, change it to 1
                    XXXXXXXX
                 OR 00000001
             Equals XXXXXXX1
             so that means leave everything the same but PIN0 of that register
             According to http://arduino.cc/playground/uploads/Learning/Atmega168PinMap2.png, PIN0 of Register C is Analog 0             
      Now that i have explained the logic on what it's doing, it's setting Chip pin 23 (Analog 0) to an output
    */
     
  PORTC |= _BV(DHT11_PIN);
  /*  same logic with the OR as above
      PORTC sets the pin(s) as high or low depending on the value (1 is high, 0 is low)
      so... change PORTC and leave all pins the same except for DHT11_PIN (which as we explained above is Analog 0) and set that as High (5V output)
  */

  Serial.begin(9600); //Enable the serial port at 9600 baud
  Serial.println("Ready");  // Print "Ready" to the serial output
}

void loop()
{
  DHT11();  //Call the function DHT11()
}

byte read_dht11_dat()
{
  byte i = 0;
  byte result=0;
  for(i=0; i< 8; i++){	
    while(!(PINC & _BV(DHT11_PIN))); // wait for 50us
    delayMicroseconds(30);	
    if(PINC & _BV(DHT11_PIN)) 
      result |=(1<<(7-i));
    while((PINC & _BV(DHT11_PIN))); // wait '1' finish


  }
  return result;
}


void DHT11()  //Function DHT11()
{
  byte dht11_dat[5];
  byte dht11_in;
  byte i;
  // start condition
  // 1. pull-down i/o pin from 18ms
  PORTC &= ~_BV(DHT11_PIN);
  /* this time we are turning the pin down, rather than up.
  Here's what's going on:
  PORTC equals PORTC Bitwise AND the inverse of the binary value of DHT11_PIN
  Now to give it a graphical representation:
      non-related example:
                  11001010
              AND 11110000
           Equals 11000000
      If you don't understand that, brush up on your bitwise math
      
      Now, this is what it's doing (assuming DHT11_PIN = 0
            _BV(DHT11_PIN) is the same as 00000001
            the inverse of that is 11111110
            now, 
                      XXXXXXXX
                  AND 11111110
               Equals XXXXXXX0
            thus only bringing PortC Pin 0 low
  */
  
  delay(18);
  PORTC |= _BV(DHT11_PIN);
  /* This (as explained above) brings it back high*/
  delayMicroseconds(40);

  DDRC &= ~_BV(DHT11_PIN);
  /* This (as explained above) brings it back low*/
  delayMicroseconds(40);

  dht11_in = PINC & _BV(DHT11_PIN);  //Set the value of the PINC registers AND _BV(DHT11_PIN) to dht11_in

  if(dht11_in){  //if dht11_in is non-zero
    Serial.println("dht11 start condition 1 not met");
    return;
  }
  delayMicroseconds(80);

  dht11_in = PINC & _BV(DHT11_PIN);  //Set the value of the PINC registers AND _BV(DHT11_PIN) to dht11_in

  if(!dht11_in){  //if dht11_in is zero
    Serial.println("dht11 start condition 2 not met");
    return;
  }
  delayMicroseconds(80);
  // now ready for data reception
  for (i=0; i<5; i++)
    dht11_dat[i] = read_dht11_dat();

  DDRC |= _BV(DHT11_PIN);  // for some reason this sets it as output again
  PORTC |= _BV(DHT11_PIN);  // this sets it high again

  byte dht11_check_sum = dht11_dat[0]+dht11_dat[1]+dht11_dat[2]+dht11_dat[3];
  // check check_sum
  if(dht11_dat[4]!= dht11_check_sum)
  {
    Serial.println("DHT11 checksum error");
  }

  Serial.print("Current humdity = ");
  Serial.print(dht11_dat[0], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[1], DEC);
  Serial.print("% ");
  Serial.print("temperature = ");
  Serial.print(dht11_dat[2], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[3], DEC);
  Serial.println("C ");

  delay(200);
}

Here is the code you will want to use.

#define DHT11_PIN 0 // ADC0

void setup()
{
  pinMode(A0, OUTPUT);  
  digitalWrite(A0, HIGH);
  Serial.begin(9600); 
  Serial.println("Ready");
}

void loop()
{
  DHT11();
}
byte read_dht11_dat()
{
  byte i = 0;
  byte result=0;
  for(i=0; i< 8; i++){	
    while(!(digitalRead(A0) & 1)); // wait for 50us
    delayMicroseconds(30);	
    if(digitalRead(A0) & 1) 
      result |=(1<<(7-i));
    while((digitalRead(A0) & 1)); // wait '1' finish


  }
  return result;
}


void DHT11()
{

  byte dht11_dat[5];
  byte dht11_in;
  byte i;
  // start condition
  // 1. pull-down i/o pin from 18ms
  digitalWrite(A0, LOW);
  delay(18);
  digitalWrite(A0, HIGH);
  delayMicroseconds(40);

  pinMode(A0, INPUT);  
  delayMicroseconds(40);

  dht11_in = analogRead(A0);


  if(dht11_in){
    Serial.println("dht11 start condition 1 not met");
    return;
  }
  delayMicroseconds(80);

 dht11_in = analogRead(A0);

  if(!dht11_in){
    Serial.println("dht11 start condition 2 not met");
    return;
  }
  delayMicroseconds(80);
  // now ready for data reception
  for (i=0; i<5; i++)
    dht11_dat[i] = read_dht11_dat();

  pinMode(A0, OUTPUT);  
  digitalWrite(A0, HIGH);

  byte dht11_check_sum = dht11_dat[0]+dht11_dat[1]+dht11_dat[2]+dht11_dat[3];
  // check check_sum
  if(dht11_dat[4]!= dht11_check_sum)
  {
    Serial.println("DHT11 checksum error");
  }

  Serial.print("Current humdity = ");
  Serial.print(dht11_dat[0], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[1], DEC);
  Serial.print("% ");
  Serial.print("temperature = ");
  Serial.print(dht11_dat[2], DEC);
  Serial.print(".");
  Serial.print(dht11_dat[3], DEC);
  Serial.println("C ");

  delay(200);
}

Note: it is possible that the sensor may not work this way, due to the fact that digitalWrite is 100x slower than port manipulation. If this is the case, you will have to use port manipulation.
Explanation coming later.

100x slower than port manipulation

Any data to support that? A quick look at the analogRead() function suggests it isn't "100" times slower.

Sorry that was a bit of an exaduration, the digitalread and write functions are about 20-50x slower based on some timing i did myself in the past, plus some reading from google, including this article: » Pin I/O performance » JeeLabs

That is what i was referring to as the problem, not so much the analog reads. I figured that the digital reads and writes were time sensitive because of the very small microsecond delays in there.

Sorry that was a bit of an exaduration

True, my mention to "analogRead()" was a slip-up. I meant digitalRead().

I find it hard to believe that Port read performance really matters when reading a relatively slow sensor like a temperature or humidity sensor.

I don't know about 100x slower but I would agree with about 30x. Proof in screenshot below.

Code to reproduce:

void setup()
{
  pinMode (A0, OUTPUT);
  pinMode (A1, OUTPUT);
}

void loop ()
{
 PORTC |= _BV(0);
 PORTC &= ~_BV(0);

 PORTC |= _BV(1);
 PORTC &= ~_BV(1);
 
 digitalWrite (A0, HIGH);
 digitalWrite (A0, LOW);

 digitalWrite (A1, HIGH);
 digitalWrite (A1, LOW);
}

The screenshot shows short pulses (125ns) for the bit manipulation, and 3792 ns (3.7917us) for the digitalWrite. 3792/125 = 30.33, which demonstrates that the port manipulation is about 30x as fast. A check with a higher-bandwidth oscilloscope confirms the short pulse width is 125 ns, so there is no major measurement error there.

The reason digitalWrite is somewhat slower is that it does more stuff ...

  • It converts the pin number to a port/bit offset by a table lookup
  • It checks the requested pin is valid.
  • It turns off PWM on that pin if required.
  • It saves the interrupt mask state.
  • It turns off interrupts.
  • It does the bit manipulation with the discovered port/bit offset
  • It restores the interrupt mask state.

This is more convenient, but it takes time. Evidently 3.66 microseconds. Still a delay(1) delays you by 1000 microseconds, so in the code you showed that probably isn't too bad.

Fortunately one can choose which method to use for your Arduino application. Direct port if speed is important or the arduino abstraction method for ease of use and readability of code, and both methods can exist in the same sketch, if you understand all the possible conflicts involved like PWM, etc.

Keep in mind that direct port access is not portable across the mega8/168/328 Vs mega1280/2560 boards as port.pin mapping is different between these two AVR 'series'.

Lefty

My concern wasn't in the reading, but in the initialisation, because the original author obviously thought timing was important, due to the very specific delayMicroseconds(). I guess we will just have to wait and see though.

I think you could compensate. For example, change:

    delayMicroseconds(30);

to

    delayMicroseconds(26);

That's an option, but it will be a pain If necessary. The serial interface looks like it could be a real pain with bad timings www.robotshop.com/PDF/dht11.pdf