Jump to content
Sign in to follow this  
touch

Reading tempature from a DS1821+ 1-Wire tempature sensor

Recommended Posts

Hello all, I'm trying to read the temperature from a DS1821+ 1 wire temperature sensor, I have had luck making it work on my Arduino but have been unable thus far to get anything other than "FF" in hex printed out over serial on my MSP430G2231 w/ my launchpad.

 

I'm using MSPhere to provide the digitalread, digitalwrite, and pinmode functions.

 

I've based my code off of this Arduino example: http://sheepdogguides.com/arduino/ar3ne1tt.htm, however, that code is for the DS1820 which is a little bit different, so I had to make some changes to my code to make it work with the DS1821+ on the Arduino, but have been unsuccessful on my launchpad as I said.

 

#include "msphere.h"                // include the MSPhere core library
#include "msphere_digital_io.h"     // include digital i/o functions

#define UART_TXD   0x02                     // TXD on P1.1 (Timer0_A.OUT0)
#define UART_RXD   0x04                     // RXD on P1.2 (Timer0_A.CCI1A)
#define UART_TBIT_DIV_2 (1000000 / (9600 * 2))
#define UART_TBIT (1000000 / 9600)
#define WIRE_PORT 7

void OneWireOutByte(int _1W_Pin, char d);
char OneWireInByte(int _1W_Pin);
void OneWireReset(int _1W_Pin);
void delayms(unsigned int ms);
void delayMicroseconds(unsigned int us);
void TimerA_UART_init(void);
void TimerA_UART_tx(unsigned char byte);
void TimerA_UART_print(char *string);

unsigned int txData;                        // UART internal variable for TX
unsigned char rxBuffer;                     // Received UART character

void setup(void)
{
WDTCTL = WDTPW + WDTHOLD;               // Stop watchdog timer

   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   P1OUT = UART_RXD;                       // RXD as IN
   P1SEL = UART_TXD + UART_RXD;            // Timer function for TXD/RXD pins
 	P1DIR = UART_TXD;    

   __enable_interrupt();

   TimerA_UART_init();                     // Start Timer_A UART
   TimerA_UART_print("DS1821+ Test:\r\n");
}

void loop(void){
OneWireReset(WIRE_PORT);
OneWireOutByte(WIRE_PORT, 0xEE);
OneWireReset(WIRE_PORT);
OneWireOutByte(WIRE_PORT, 0xAA);
TimerA_UART_tx(OneWireInByte(WIRE_PORT));
delayms(500);
}
void OneWireReset(int Pin) // reset.  Should improve to act as a presence pulse
{
digitalWrite(Pin, LOW);
pinMode(Pin, OUTPUT); // bring low for 500 us
delayMicroseconds(500);
pinMode(Pin, INPUT);
delayMicroseconds(500);
}

void OneWireOutByte(int Pin, char d) // output byte d (least sig bit first).
{
  char n;

  for(n=8; n!=0; n--)
  {
     if ((d & 0x01) == 1)  // test least sig bit
     {
        digitalWrite(Pin, LOW);
        pinMode(Pin, OUTPUT);
        delayMicroseconds(5);
        pinMode(Pin, INPUT);
        delayMicroseconds(60);
     }
     else
     {
        digitalWrite(Pin, LOW);
        pinMode(Pin, OUTPUT);
        delayMicroseconds(60);
        pinMode(Pin, INPUT);
     }

     d=d>>1; // now the next bit is in the least sig bit position.
  }

}

char OneWireInByte(int Pin) // read byte, least sig byte first
{
   char d, n, b;

   for (n=0; n<8; n++)
   {
       digitalWrite(Pin, LOW);
       pinMode(Pin, OUTPUT);
       delayMicroseconds(5);
       pinMode(Pin, INPUT);
       delayMicroseconds(5);
       b = digitalRead(Pin);
       delayMicroseconds(50);
       d = (d >> 1) | (b<<7); // shift d to right and insert b in most sig bit position
   }
   return(d);
}

void delayMicroseconds(unsigned int us)
{
while(--us)
{
__delay_cycles(1);
}
}

void delayms(unsigned int ms)
{
while(--ms)
{
__delay_cycles(1000);
}
}

//------------------------------------------------------------------------------
// Function configures Timer_A for full-duplex UART operation
//------------------------------------------------------------------------------
void TimerA_UART_init(void)
{
   TACCTL0 = OUT;                          // Set TXD Idle as Mark = '1'
   TACCTL1 = SCS + CM1 + CAP + CCIE;       // Sync, Neg Edge, Capture, Int
   TACTL = TASSEL_2 + MC_2;                // SMCLK, start in continuous mode
}
//------------------------------------------------------------------------------
// Outputs one byte using the Timer_A UART
//------------------------------------------------------------------------------
void TimerA_UART_tx(unsigned char byte)
{
   while (TACCTL0 & CCIE);                 // Ensure last char got TX'd
   TACCR0 = TAR;                           // Current state of TA counter
   TACCR0 += UART_TBIT;                    // One bit time till first bit
   TACCTL0 = OUTMOD0 + CCIE;               // Set TXD on EQU0, Int
   txData = byte;                          // Load global variable
   txData |= 0x100;                        // Add mark stop bit to TXData
   txData <<= 1;                           // Add space start bit
}

//------------------------------------------------------------------------------
// Prints a string over using the Timer_A UART
//------------------------------------------------------------------------------
void TimerA_UART_print(char *string)
{
   while (*string) {
       TimerA_UART_tx(*string++);
   }
}
//------------------------------------------------------------------------------
// Timer_A UART - Transmit Interrupt Handler
//------------------------------------------------------------------------------
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A0_ISR(void)
{
   static unsigned char txBitCnt = 10;

   TACCR0 += UART_TBIT;                    // Add Offset to CCRx
   if (txBitCnt == 0) {                    // All bits TXed?
       TACCTL0 &= ~CCIE;                   // All bits TXed, disable interrupt
       txBitCnt = 10;                      // Re-load bit counter
   }
   else {
       if (txData & 0x01) {
         TACCTL0 &= ~OUTMOD2;              // TX Mark '1'
       }
       else {
         TACCTL0 |= OUTMOD2;               // TX Space '0'
       }
       txData >>= 1;
       txBitCnt--;
   }
}      
//------------------------------------------------------------------------------
// Timer_A UART - Receive Interrupt Handler
//------------------------------------------------------------------------------
#pragma vector = TIMERA1_VECTOR
__interrupt void Timer_A1_ISR(void)
{
   static unsigned char rxBitCnt = 8;
   static unsigned char rxData = 0;

   switch (__even_in_range(TAIV, TAIV_TAIFG)) { // Use calculated branching
       case TAIV_TACCR1:                        // TACCR1 CCIFG - UART RX
           TACCR1 += UART_TBIT;                 // Add Offset to CCRx
           if (TACCTL1 & CAP) {                 // Capture mode = start bit edge
               TACCTL1 &= ~CAP;                 // Switch capture to compare mode
               TACCR1 += UART_TBIT_DIV_2;       // Point CCRx to middle of D0
           }
           else {
               rxData >>= 1;
               if (TACCTL1 & SCCI) {            // Get bit waiting in receive latch
                   rxData |= 0x80;
               }
               rxBitCnt--;
               if (rxBitCnt == 0) {             // All bits RXed?
                   rxBuffer = rxData;           // Store in global variable
                   rxBitCnt = 8;                // Re-load bit counter
                   TACCTL1 |= CAP;              // Switch compare to capture mode
                   __bic_SR_register_on_exit(LPM0_bits);  // Clear LPM0 bits from 0(SR)
               }
           }
           break;
   }
}

Share this post


Link to post
Share on other sites
Am i missing some thing? there is no main... I didn't think you could code the launch pad with the setup and loop function.

 

Exactly. The Arduino abstracts this stuff away from the user.

 

Try placing the following code above your setup() function.

void main() {
 setup();
 for(; loop();
}

Share this post


Link to post
Share on other sites

I didn't think it would compile with out a main.

 

could be something neat to add to the MSPhere.

 

#define ARDUINO_SETUP int main(void){setup();for(;;)loop();return 0;}

Share this post


Link to post
Share on other sites

MSPhere provides the main()->msphere.c

void main() {
       init();
setup();
for (; loop();
}

 

I have no trouble compiling or running, and in fact it seems to be sort of working because If i unplug the sensor I start to get 0's printed out on my terminal, but when It's plugged in I only get FF printed.

Share this post


Link to post
Share on other sites

I tried out your code on my launchpad and I think the problem lies in the delay routines.

 

I tried to measure them with my scope.

 

For my setup, the function call "delayMicroseconds(1);" measures out to 25 microseconds.

 

So, if this is true in your case then it would make sense that you read 0xFF because the onewire bus is high when your routines are trying to read it.

 

What do you think?

Share this post


Link to post
Share on other sites

Okay, I tested this a little more and this is what I found.

 

This is my test code:

#include  

void main(void)
{
 WDTCTL = WDTPW +WDTHOLD;                  // Stop Watchdog Timer
 P1DIR |= 0x13;                            // P1.0,1 and P1.4 outputs
 P1SEL |= 0x11;                            // P1.0,4 ACLK, SMCLK output

 while(1)
 {
   P1OUT |= 0x02;    	                    // P1.1 = 1
   __delay_cycles(1);
   P1OUT &= ~0x02;                         // P1.1 = 0
   //__delay_cycles(1);
   //P1OUT |= 0x02;    	                    // P1.1 = 1
 }
}

 

I selectively add in the delay routine and it successfully adds 1usec to the execution time.

 

However, the time it takes for an output pin to change is about 4x as long. Without the delay call, the main routine creates a pulse train with a period of ~9.7usec. It is high for 3.8usec and low for 5.8usec.

 

So it looks like it takes 2usec for the main routine to loop back around and start over again.

 

The next step is to time out the function call overhead ie: delayMicroseconds(5) to see how much it adds to the process.

 

After that, the next step is to time out the one wire functions to see if they are close to the specified read and write time constraints.

Share this post


Link to post
Share on other sites

Okay, I added in the function call so I could measure the time that it adds.

 

Here's my test code:

#include  

void delayMicroseconds(unsigned int us)
{
  while(--us)
  {
  __delay_cycles(1);
  }
}

void main(void)
{
 WDTCTL = WDTPW +WDTHOLD;                  // Stop Watchdog Timer
 P1DIR |= 0x13;                            // P1.0,1 and P1.4 outputs
 P1SEL |= 0x11;                            // P1.0,4 ACLK, SMCLK output

 while(1)
 {
   P1OUT |= 0x02;    	                    // P1.1 = 1
   delayMicroseconds(1); // 24usec
   P1OUT &= ~0x02;                         // P1.1 = 0
 }
}

With the function call in, the high pulse is 24usec long.

 

With the function call out, the high pulse is 3.8usec long.

 

So, it looks like the function call adds about (24 - 3.8) = 20.2usec of unexpected execution time.

 

Here's my measurements of the impact of calling the delay routine:

delayMicroseconds(1) -> ~24usec

delayMicroseconds(2) -> ~31usec

delayMicroseconds(3) -> ~39usec

delayMicroseconds(4) -> ~45usec

delayMicroseconds(5) -> ~52usec

 

The next step would be to test out a different method of using the __delay_cycles() function.

Share this post


Link to post
Share on other sites

I changed the delay routine to this:

#include  
#define delayMicroseconds(n) __delay_cycles(n)

void main(void)
{
 WDTCTL = WDTPW +WDTHOLD;    // Stop Watchdog Timer
 P1DIR |= 0x13;                             // P1.0,1 and P1.4 outputs
 P1SEL |= 0x11;                             // P1.0,4 ACLK, SMCLK output

 while(1)
 {
   P1OUT |= 0x02;    	                    // P1.1 = 1
   delayMicroseconds(1); 
   P1OUT &= ~0x02;                         // P1.1 = 0
   //delayMicroseconds(1);
   //P1OUT |= 0x02;    	                    // P1.1 = 1
 }
}

First, notice that I created a #define so that you could retain the naming convention for the delay routine.

 

This method has a dramatic effect on the timing of the delay call. All the overhead time is now gone.

 

delayMicroseconds(0) -> 3.8usec

delayMicroseconds(1) -> 4.8usec

delayMicroseconds(2) -> 5.8usec

delayMicroseconds(3) -> 6.8usec

delayMicroseconds(4) -> 7.7usec

delayMicroseconds(5) -> 8.7usec

 

It seems quite reliable to use the __delay_cycle(n) routine over the original delay routine. As long as the speed of the MCLK is known and configured. The default MCLK speed is set to the default DCO speed which is 1MHz in this case. A happy co-incidence.

 

If I was going to run my processor at a different speed, I would have to alter my delay routine accordingly.

 

The next thing to do is to see how this new method impacts the one wire routines. Maybe there's some refactoring to do there too?

Share this post


Link to post
Share on other sites

If you need more precise delays in single us range, my suggestion is to bump up the clock. At 1MHz, you have to take every instruction into account, but at let's say 8MHz, you can be few cycles off.

Share this post


Link to post
Share on other sites

I agree. I desire to run the processor at 8 or 16MHz.

 

I spent the rest of the afternoon trying to figure out why and how to calibrate the processor for speeds other than 1MHz.

 

I still haven't mastered that topic yet. But it seems that someone has in these two threads:

1. viewtopic.php?f=10&t=239

2. viewtopic.php?f=10&t=234

 

Once I do master it, I can come back and revisit this delay routine tuning and complete the 1-Wire temperature reading task.

 

On the 1-Wire topic, we might be able to use a interrupt timer routine to compute all the various delays required if we ran the processor higher than 1MHz.

 

For example, at 16MHz, one clock cycle would be 63ns and one major 1-Wire timing parameter is 480us. If you divide 480us by 63ns you then get 8000 cycles. That's enough counting that it could be done by TimerA. And like RobG said above, it would give us a lot of wiggle room with the overhead associated with function calls.

 

Just to be complete with calculations, if the processor was set to 8MHz then a single clock cycle time would be 125ns. Then if you divide 480us by 125ns, you get 2000 cycles for that critical delay time. Again, that's pretty good resolution. I bet that's what the Arduino does.

Share this post


Link to post
Share on other sites

I would definitely go with timer rather than delays.

Also, if you decide to use 1MHz clock and you need single us delays, use one or more __no_operation().

__no_operation() will insert NOP, a single cycle delay.

Share this post


Link to post
Share on other sites

I was thinking a little bit about this, and here's what I would do (or will do once I get my samples.) One port as input with pull-up resistor, one as output connected to the base of transistor, which collector is connected to the input port and DS1821 data pin. I know I can do it with one port but for now, I will use two.

Clock set at 4 or 8MHz, some counter and data variables, timer toggle variable, 2 values for CCR, one that will give me 10us delay, the other 60-80us.

In the timer interrupt routine, the first thing that will happen is testing timer toggle (flipping it at the same time.) Each time we will change CCR between 10/60us. When interrupt occurs at the end of 60us, it will be our start of slot. At the end of 10us, it will be time for sampling or changing output, updating counters, massaging the data. Also, I will be reusing my 7 digit display setup, so here's where I will cycle digits since we will have plenty of time to do it.

I think that should work pretty well.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...