Dejan Levec

DS18S20 and MSP430

Couple of weeks ago, when I was thinking about what temperature sensors to use with my heating controller, I remembered DS18S20 and instantly order samples from Maxim.

Today I finished programming software for main controller – MSP430 chip and I want to show you, how I got those sensors working by bitbanging data pin. Sensors are connected in parasitic mode (which may not be such a good idea since turning oil burner/water heater on and off causes some unwanted interference, and it certainly doesn’t help that data & ground wires are near AC cables). I also found out, that chip’s internal pull-up isn’t powerful enough to drive temperature sensor while it’s converting temperature.

How to talk to one-wire DS18S20 / DS1820 sensor?

  1. Reset
  2. Check if there is a device present
  3. Send skip ROM command
  4. Send convert command
  5. Reset
  6. Send skip ROM command
  7. Send read scratchpad command
  8. Read byte from the bus

Firstly, we need to reset the device, then it’s necessary to sample bus to see if device responded and after that we send skip command, because we only have one sensor connected to the line and don’t care about it’s unique ID and then we request from sensor to convert (take temperature).

After that we do another reset, then skip ROM and read scratchpad commands, and after that we read received data – scratchpad from the device.

I talked more about specific commands and which data scratchpad contains previously on this blog, but I will nonetheless paste code which converts temperature to human readable float number:

int16_t temp = (((int16_t)scratchpad[1]) << 8) | scratchpad[0];
float temp2 = temp*0.5 – 0.25+((scratchpad[7]-scratchpad[6])/scratchpad[7]);

 

How to reset the bus?

P2OUT &= ~bi; //drive bus low
delay_us(480); //delay 480 us

P2OUT |= bi; //release bus
delay_us(70); //delay 70 us

//Presence
P2DIR &= ~bi; //input

We drive bus low for 480 us, and the release it and change pin from output to input. (bi is data pin bit)

After that we can check if device is present on the bus by sampling the bus. Device pulls data line high after reset for about 410 us.

 

How to send a byte?

Well, the better question is how to send a bit. Every byte contains 8 bits and when dealing with that low level of controlling the device with big banging you will need to send bits instead of bytes.

Bit can be presented as logical high ‘1’ or logical low ‘0’. Logical low can be send with the following code:

P2DIR |= b;//output
P2OUT &= ~b; //drive bus low
delay_us(60);

P2OUT |= b; //release bus
delay_us(10);

You need to drive bus low for 60 us and then release it (drive it high) for 10 us.

On the other hand, logical high can be represented as driving bus low for 6 us and then high for 64 us:

P2DIR |= b;//output
P2OUT &= ~b; //drive bus low
delay_us(6);

P2OUT |= b; //release bus
delay_us(64);

With the help of those two functions we can write bytes to the bus like this:

for(int i = 0;i<8;i++) {
if(b&0x01) {
write_1(bi);
}else{
write_0(bi);
}

b>>=1;
}

If I understand this right (I mostly overslept class about bytes, bits, bitwise operators and other low level stuff about computers), you write byte to the bus by shifting bits to right and comparing them to 0x01 to decide if we want to write logical high or logical low.

Let’s for example take hexadecimal number 0xCC which indicates temperature sensor to skip ROM check:
0xCC is 204 in decimal and 1100 1100 in binary format.

Firsly, we check if this is true: 0xCC & 0x01. Ampersand (&) represents bitwine AND operator which checks every single bit of both numbers and results in 1 if bits in same place in both numbers are 1. Otherwise it results in 0.

For example:
0010 1011 &
0100 0010 equals:
0000 0010
because only second bit from the right side is true in both numbers.

0xCC & 0x01 equals:
1100 1100 & 0000 0001 which returns 0000 0000 because there the last bit in first number is  not positive. Because 0000 0000 equals logical false, the condition of if statement is false and we use function to write 0 on the one-wire bus.

Secondly, we right shift bits for one place and get 0110 0110.

This repeats eight times, to write all bits of the byte to the bus. This type of bit shifting means that device receives byte from least significant bit (LSB) or right-most bit to the most significant bit.

Now we have taken care of resetting and writing bytes to the device. Only thing left to do is to read received byte to actually read the temperature from the sensor.

 

Reading byte from device

Again, we need to read byte from device bit by bit and here is the code for reading a single bit:

P2DIR |= bi;//output
P2OUT &= ~bi; //drive bus low
delay_us(6);
P2OUT |= bi; //release bus
delay_us(9);

P2DIR &= ~bi;//input
boolean x = (P2IN & bi);
delay_us(55);

We make pin an output, drive bus low for 6 us and release it for 9 us. Then we change direction of the pin to input and read bit’s state. After that we need to wait 55 us before reading another one.

I like to complicate things so here is my code for reading a whole byte (I’m quite certain that there is a better way to doing this, but this is what I came up with):

byte j=0x01;
byte r=0x00;
for(int i = 0;i<8;i++) {

if(read_bit(bi)) {
r=r| j;
}else{
r=r& ~j;
}

j<<=1;
}

So, what we do here is to define two variables, first equals 0x01 or 0000 0001 and the second is 0. After that we check is we read logical true or false as current bit and modify variable r accordingly with the help of variable j, which is shifted left after every turn.

Again, we are dealing with byte represented from the least significant bit forward.

r = r | j represents bitwise OR which compares each bit of both bytes and results in 1 if bit of either one of bytes is 1 or true.

If, for example, we are trying to read 0xAC (1010 1100) we start with the rightmost bit/LSB and go to the left:

1.) First bit is 0, so we execute  r & ~j which means:
0000 0000 & ~0000 0001 and ~ (tilde) inverses all bits, so we get:
0000 0000 & 1111 1110 which leaves alone every bit except the rightmost bit, which is set to 0.

2.)After that we shift j to left by 1, which gives us: 0000 0010.

Second bit is 0, so we again clear that bit from the final byte by using 0000 0000 & 1111 1101 to get: 0000 0000.

3.) Third bit is 1 and by using 0000 0000 | 0000 0100 we get: 0000 0100.

4.) Fourth bit is 1: 0000 0100 | 0000 1000 equals 0000 1100

5.) Fifth bit is 0: 0000 1100 & 1110 1111 leaves byte unchanged

6.) Sixth bit is 1: 0000 1100 | 0010 0000 equals 0010 1100

7.) Seventh bit is 0, byte stays the same

8.) Eight and the last bit is 1 which means that: 0010 1100 | 1000 000 equals 1010 1100 and we get our final number 0xAC.

 

That is all you need to know to communicate to a single one-wire temperature sensor from DS1820 family. It actually seems a lot easier than it seemed when I read this from different datasheets.

Here is the link to code containing all functions needed to get temperature data from this sensor: ds18s02

You need to call function get_temp to get the temperature and it’s only argument is bit from second port of the micro controller. Code was written and tested using Energia IDE (which has the same crappy IDE as Arduino, but libraries are excellent) so it should work fine with msp430 gcc compiler.

Leave a Reply

Your email address will not be published. Required fields are marked *