Saturday, December 27, 2014

1 Wire Interface - DS18B20

Well well, here we go, I opened the DS18B20 datasheet, and we start rolling. Well there should be a reset / presence pulse, then we have several other to go thru.
5 years later ...
Ok, sorry got carried away, but managed to get that reset thingy. It was just making the pin output, so it can get logic low, waiting for 480 microseconds, making it input again (high-z), then waiting for another 40 microseconds or so, then reading, if it was kept high by PULL-UP resistor, then we connected something wrong, or in other words - trouble. The best case scenario is - you get the wire pulled low when you do the reading at that time.
  // send reset / presence pulses
  DDRB |= _BV(PB0); // make it output, keep it low
  PORTB &= ~_BV(PB0);
  _delay_us (480); // wait for some time
  DDRB &= ~_BV(PB0); // make it into input again
  _delay_us (60);
  if (! (PINB & _BV(PB0)) ) {
    PORTA ^= _BV(PA0);
    send_rw ("Got response ...\n\r");
  } else
    send_rw ("No response, check connections \n\r");
In code, it would look something like this. So I managed to get response message in my debug terminal when the IC was in breadboard, while it was gone, I got error message. First step done! Considering that there is still 4 main function-y things yet to go, I think making it all functions will come handy, you know, less code, less effort.
Update: Seems I forgot to mention the basic principle of 1 wire interface. You basically have a master device (e.g. MCU) and one or more slave devices. You have 1 data bus as you might have guessed, which is pulled up by a resistor (5-10k usually). So all the devices are 3 state - high, low, high impedance. You work by pulling the bus low or going hi-z. Here is the schematic for DS18B20
Spoiler: we power it normally - not the parasite power mode

Ok, so now lets see the write and read commands for 1 wire interface. I'm basically telling you what I understood from the DS18B20 datasheet.

Write command

You send data using write slots, which has a duration of 60 us. It all starts by pulling the data line low (making the pin output). And then if you transmit 1, you should get back to hi-z within 15 us, to let pull-up resistor pull the level to high. Of course to finish the slot, you should wait another ~45 us.
In case of writing 0, you should keep the data bus low for hole 60 us. To finish transmitting one bit, you should pull the bus back to high (go hi-z). And after each write slot, you should give it a recovery time at least 1 us. The code I wrote looks like..
void write_1 (void) {
  // pull the bus low
  DDRB |= _BV(PB0);
  // wait within 15 us
  _delay_us (10);
  // go hi-z
  DDRB &= ~_BV(PB0);
  // wait to finish waiting 60 us
  _delay_us (50);
}

void write_0 (void) {
  // pull the bus low
  DDRB |= _BV(PB0);
  // wait 60 us
  _delay_us (60);
  // go hi-z
  DDRB &= ~_BV(PB0);
}
This functions will transmit either 1 or 0. Just one bit, so in order to transmit 8 bits, or 1 byte, you've got to call it 8 times. So, the function for writing a whole byte would look like
void write (uint8_t data) {
  // lsb first
  register uint8_t i = 0;
  for (i=0; i<8; ++i) {
    if (data & _BV(i)) write_1();
    else write_0();
    _delay_us (1);
  }
}
Then I asked myself, will this work ? One way to check - read the datasheet. I have only one DS18B20 connected, so I didn't need to do ROM search, I could just use the Read ROM(0x33) command to get 8 bytes of info back. The 8 bytes are - family code, 6 serial number bytes, and CRC byte. But in order to receive them I need to read from the bus. So here comes the second part..

Read command

By far how I could understand - you read and write bits, separately. So read command consists of bit-bit reading. In order to start read slot, you pull the bus low, wait for at least 1 us, then go hi-z. After that you wait ~ 15 us more, and sample data. If the pin is high, you read it as 1, otherwise it's 0. And you finish the read slot waiting enough, to make the slot ~60 us. You also need at least 1 us time between reads. Well yeah, this interface is pretty slow.
uint8_t read (void) {
  uint8_t rx = 0;
  register uint8_t i=0;
  for (i=0; i<8; i++) {
    // pull the bus low
    DDRB |= _BV(PB0);
    // wait 1 us
    _delay_us (1);
    // go hi-z
    DDRB &= ~_BV(PB0);

    _delay_us (10);

    if (PINB & _BV(PB0)) {
      // rx 1
      rx |= _BV(i);
    } else {
      // rx 0
      rx &= ~_BV(i);
    }

    _delay_us (50);
  }
  
  _delay_us (2);
  return rx;
}
Yeah, in case I forgot to mention, all transmissions are LSB first. So we read bit by bit to read a whole byte by that function. After that, I just tried sending Read ROM(0x33) and looking at output.
  // ROM commands
  
  write (0x33); // read rom (64 bit)

  family_code = read ();
  debug_out ("FC: 0x%02X", family_code);
  for (i=0; i<6; ++i) {
    serial_code[i] = read();
    debug_out ("Serial code [%u]: 0x%02X", i, serial_code[i]);
  }
  CRC_byte = read();
  debug_out ("CRC byte: 0x%02x", CRC_byte);
The output was this.

Meaning, we managed to establish connection! Now by following the protocol we should be able to get temperature readings.. But.. There is yet one 'but' I want to see. If I indeed got the address correctly, I should be able to do the checking that the datasheet says (page 6).

CRC checking

So the first byte is indeed 28h - the family code is correct. To check the next 64 bytes, we'll need to use the cyclic redundancy check (CRC byte) that we got in the last place. Now looking at the datasheet, there is this shift register made generator.. You must realize that this step is important, as with it you can somewhat see if that data was corrupted, and if it was, ask for new data.

Now let's see how we can do the checking with minimum effort..
Some time later
[added later] I wrote another post dedicated to CRC checking, turn's out there is a pretty handy library in avr-libc for that. Link can be found here.

Once again - RESET / PRESENCE

After looking at the datasheet once again, I realize that this step is the first step to every command you send. So to write less code, we will turn this into a function.
void reset_presence (void) {
  // send reset / presence pulses
  DDRB |= _BV(PB0); // make it output, keep it low
  PORTB &= ~_BV(PB0);
  _delay_us (480); // wait for some time
  DDRB &= ~_BV(PB0); // make it into input again
  _delay_us (60);

  if (! (PINB & _BV(PB0)) ) {
    PORTA ^= _BV(PA0);
    send_rw (">>> Got presence pulse ...\n\r");
  } else
    send_rw (">>> No response, check connections \n\r");
}
So the steps for sending a command are:
  1. Reset / Presence pulses - if we don't get presence pulse, there is a connection problem
  2. ROM command - like select certain device, or skip ROM selection
  3. The function command - e.g. Convert temperature.
The only exceptions are Read ROM and Alarm Search commands. After those, you can't send any functioning commands.

Temperature Reading!

Ok let's finish this up by finally getting the temperature on our terminal screen.
As it was described above, we should do the reset thingy, then a ROM command, then some function command. Assuming that we don't want to change any alarms or configuration settings (too much effort), we will go all lazy and just get temperature in 12 bit mode. Our steps will be:
  1. Reset/Presence & Read ROM
  2. Reset/Presence & Match ROM & Convert T
  3. Reset/Presence & Match ROM & Read Scratchpad
Where with first step we just get the address of our slave device, then we select it, telling it to start converting temperature. We wait until it finishes, then we select him again and get the data out of it's scratchpad - the place where it holds results (and much more). I know, it is weird that we use Read ROM and Match ROM, because in this example we have single slave IC, and it is such a drag, but just to make sure everything we are doing is right (kindof). So less talk more code.

Step 1

Yess, you guessed it, the part from above, where we store family_code, serial_code, and CRC_byte. No need to rewrite it here.

Step 2

  // send reset / presence pulses .. again
  reset_presence ();

  // send match ROM (0x55) command
  debug_out ("Sending MATCH ROM (0x55) command..");
  write (0x55);
  // send ROM 64 bit data
  write (family_code);
  for (i=0; i<6; ++i)
    write (serial_code[i]);
  write (CRC_byte);

  // send convert t (0x44) command
  debug_out ("Sending Convert T (0x44) command..");
  write (0x44);
Important - after convert-t command, we should either wait for some time, hoping it will finish, or keep asking if it did or not. It will read 0 until it's doing something, and 1 when it will finish. So..
  while (read() == 0) _delay_us (1);
  debug_out ("Finished converting temperature..");

Step 3

And yet again, yes, you guessed it.. or not. The Reset/Presence & Match ROM part is same from above. The Read Scratchpad will return back 9 bytes, first two of which will be the data we are after.
  // send READ SCRATCHPAD (0xBE)
  debug_out ("Sending READ SCRATCHPAD (0xBE) command..");
  write (0xBE);

  uint8_t scratchpad[8];
  for (i=0; i<9; ++i) {
    scratchpad[i] = read();
    debug_out ("Scratchpad[%u] = 0x%02x", i, scratchpad[i]);
  }
  debug_out ("Finished READ SCRATCHPAD (0xBE).");
After all this changes, my terminal looked something like this

NOW! To wrap it all out, we have those two bytes, which describe the temperature around. I am not even sure if this part works correctly, but I sure hope so.
  double temperature = 0;
  uint8_t temp = 0;
  temp |= (scratchpad[1]&0x03);
  temp <<= 4;
  temp |= ((scratchpad[0]&0xF0)>>4);

  temperature = temp;
  for (i=0; i<4; i++)
    if (scratchpad[0] & _BV(i))
      temperature += 1.0/(1<<(4-i));
  
  if (scratchpad[1] & 0xF0) temperature *= -1;

  send_rw (">>> Calculated temperature: %.4f (c)", temperature);
I basically take the bytes, and convert them into a floating number, and send it back to the terminal. And I get:
>>> Calculated temperature: 16.8125 (c)
All the code together with the makefile can be found here.
However, sorry, this didn't turn out as well as I expected it to be, it's too messy, but hope you found it interesting and entertaining .. If there was anybody out there reading this. I'll see when I write a library for this one, and it goes into To post list, together with Encoder library. And that's all for this time. Later!

Tuesday, December 23, 2014

Change of plans

Well well, good afternoon or whatever the time it is for you out there. I figured I should post some more about what this blog is about. As I am not a good teacher, and I'm sure this was done, I won't be posting the basic stuff, like port manipulation, or timer setups. That would be a waste of time for me, and it can be found in the datasheet anyway, so, it will be rather more intermediate stuff, where we get a goal to make something work, and we fail .. or in some rare cases, it works as it was planned. After last rotary-encoder post, I think I will do an RGB led, just to demo the library that I wrote, and then I think I will do a exploring 1 wire post. So, wait for it .. maybe.

Messing around - the encoder

Well well, helo there yet again. So yeah, this time we will be messing around a simple 3 pin encoder.
It has three pins as you might have guessed, and that will be Common pin, pin A, pin B. The way it works is pretty simple. Once you turn it in any direction it sends pulse to both A and B pins. Depending on which way you turn, A will be the first, or vice versa. It works kindof like two buttons, and one gets pushed before the other. So what do we need in order to work with buttons ? Well - pull-up resistors, a little bit of debouncing and of course some magic to put it all together. Just look at the block diagram.
The PIN-A and PIN-B will be representing the pins on the microcontroller. This is actually boring, the fact that I have done this already. Oh well, the next project will be about 1 wire, so next time I will write as my progress will go. Whatever, seems I have got out of the track. Now, Let's choose two pins for this, any GPIO will work.
I chose PD2 and PD3 on my ATmega16. Of course we can use external pull-up resistors, like 10k will work just fine, but for convenience we will use the internal pull-ups of AVR. And we will need to somehow see, is it really working or what. We could technically use an LCD display, or send data back to PC with UART and other ways of overkilling it, but the for sake of simplicity, lets just connect and LED to one of AVR's PWM channels, thus letting us to control the brightness of the LED by turning the encoder. So with no further ado, let's start throwing some code in.
We begin with adding the libraries and some boring stuff as always.
#include inttypes.h
#include avr/io.h
#include util/delay.h
We have inttypes.h for all the variable types like uint8_t and uint16_t, and avr/io.h for all the register definitions. And you might have guessed it, delay.h for setting up delays.
Then we go to the unpleasant part - reading the datasheet. Well, it wasn't pleasant for me at first as well, but trust me, it will get easier once you do some messin' around. We will configure Timer counter 1 to generate PWM to control the brightness of an LED with it. So let's write a function for that..
void init_timer1(void) {
  /* 
   * Timer configuration:
   * Fast pwm mode (8 bit)
   * Freq: (divider: 1)
   * Non inverting mode
   */
  TCCR1A = _BV(COM1A1)|_BV(COM1B1)|_BV(WGM10); //|_BV(WGM11);
  TCCR1B = _BV(WGM12)| /* _BV(CS12)| */_BV(CS10);
}
IMPORTANT - keep in mind, this is the configuration for ATmega16 it might be different for your model. Just look up the datasheet, search for Timer counter register description or whatever, and try to configure it for Fast PWM mode. An oscilloscope might come handy for this kind of job, but in case you find some complications, just comment about it. It took me some time to fully understand timer/counters. In case you get the question: "what the heck will this do", it is probably a good idea to look at the datasheet (page 110 for ATmega16). Now to test if this is working, connect an LED to OC1A or OC1B (using a resistor might be a good idea) and test it with this main routine.
int main (void) {
  init_timer1();
  DDRD |= _BV(4)|_BV(5); // set PWM pins as outputs
  uint8_t ocr = 0;
  while (1) {
    ocr ++;
    OCR1A = ocr;
    _delay_ms (1);
  }
  return 0;
}
After this, the LED shold start getting all brighter, until it reaches it's criteria, then it will start over. I'll leave the explanation to you... (ocr is a uint8_t, which goes from 0-255, thus when you do (255)++, it starts over from 0). Notice that I turn both channels in init_timer1, but I turn on the OC1A channel, so be sure to connect the LED to the right pin.
Going on, if you got it working right, good for you. If however not, check this few things:
  • LED connection (anode/cathode - look twice)
  • Timer configuration
  • DDR configuration
Oh .. totally forgot, you might ask, how the heck do I get this to the microcontroller. Well, pal, you should compile it using avr-gcc, do some jiggling to get .hex file, and then using avrdude upload it to your MCU. Here's a detailed link to it: here Spoiler: I use linux, and you better do as well. But in case you don't, download win-avr and do the same steps. Also you'll need some kind of programmer. It would be practical to use some kind of makefile, but will talk about it later, so, now assuming you managed to get the LED fading in-out kindof, I will test it myself and continue ...
Yeah, sure enough, it is working for me. So lets get on with - do the jiggling for the encoder. To keep it simple, let's write it in a function: enc_init
void enc_init (void) {
  // set input pins
  DDRD = ~ ( _BV(PD2)|_BV(PD3) );
  // set inner pullups
  PORTD |= ( _BV(PD2)|_BV(PD3) );
  // ready to GO!
}
So, with this we set Data direction registers to make pins ready to be later read, and setting inner pull-ups using PORT registers. Look up the datasheet for those registers, they are the basic ones, I assume that whoever the read is, has some basic knowledge of C and AVR, but if you don't, no need to worry, you can always learn. Then we will need to make a change in the main routine, now saying it to read encoder value, and making decision based on the data read. As it was mentioned, encoder sends pules on both A and B pins, however, depending on which side you turn, A comes first, or vice versa. Now, for doing this we will be using the most common way - that is keeping track of last encoder position, reading the new one, and making decision on what to do. To avoid the noise and the bouncing that encoder brings, we will do it every 5 milliseconds. The proper way of doing it would be with another timer/counter with interrupt service routines, but this post got way complicated anyways, so I'll try to avoid making it any harder. Here is how it will go, let's first write a simple function, that returns an integer, depending on the encoder's pin's values. We'll call it get_enc_code, and it will take no arguments.
/* 
 * Returns grays code:
 * 0 - none
 * 1 - only B
 * 2 - only A
 * 3 - both
 */
uint8_t get_enc_code (void) {
  if (PIND  _BV(PD2))
    // not A
    if (PIND  _BV(PD3))
      // none
      return 0;
    else
      // B
      return 1;
  else
    // A
    if (PIND  _BV(PD3))
      // only A
      return 2;
    else
      // both
      return 3;
}
As it was mentioned, we used pull-up resistors, and judging by name, the status of the pin when nothing is moving should be pulled up, i.e. HIGH or 1 or true or whatever you call it. It will be a logical one, which can be read from the PIND register. It consists of 8 bits, responding for all the 8 pin inputs of PORTD. To get the value of that one pin (e.g PD2) we use logical and operator. However, you probably know about this, so, if both pins are 1, then encoder is not moving. In case on of the pins gets low (i.e. encoder moves) we will notice change, either A or B will come first, and then they both will turn low. You see where I'm going ?
YEss! You got it, by keeping track we can see if it was A or B turning first, and we can say if it was clockwise or counter clockwise. Let's take it to code level shall we ?
int main (void) {
  init_timer1();
  enc_init();
  DDRD |= _BV(4)|_BV(5); // set PWM pins as outputs
  uint8_t ocr = 0, enc_last_code=0, enc_code;
  uint8_t enc_rotation = 0; // 0 can be our forever..
  // 0 - no rotation
  // 1 - cw
  // 2 - ccw
  while (1) {
    enc_code = get_enc_code();
    if (enc_code == 3) {
      // we got to point where both A and B are low
      // sombody turned the darn knob
      // now checking the last position...
      if (enc_last_code == 3)
 enc_rotation = 0; // this part is for debouncing
      else if (enc_last_code == 2) // from A - both
 enc_rotation = 1;
      else if (enc_last_code == 1) // from B - both
 enc_rotation = 2;
    } else
      enc_rotation = 0;

    enc_last_code = enc_code;
    
    if (enc_rotation == 1) ocr += 10;
    if (enc_rotation == 2) ocr -= 10;

    OCR1A = ocr;
    _delay_ms (2);
  }
  return 0;
}
Ok ok, wait, I see what you might think, shit just got dark. But nope, all the code is really simple to understand. You might need to jiggle around that delay value to make it work for your encoder. Also I think there were encoders that give several pulses on one turn, however, mine is simpler, it gives only one. Back to code ... It's the realisation of the idea, and when we recognize that encoder was turned, depending on which way - we either increase or decrease the value of the output compare register, thus making the LED light brighter or darker .. However, I kind of lost you on the way here. The full file is here. Yeah .. kindof really lost the trace, what was I talking about ..hmm. Yeah, the main idea was to demonstrate my library, but my time is coming up to it's end, so I guess it will stay for later.
So this was all it was for today, hope you enjoyed. In case you don't like my style, please leave a comment, I'll try to fix it. Farewell!