Sunday, August 2, 2015

Getting started with ESP8266, flashing the latest SDK

As promised, this is the ESP8266 post. So, if you have been reading about IOT projects, chances are you already heard or even used one of these. But in case you didn't, ESP8266 is a very cheap WiFi to serial converter .. or so they call it. It easily gives access to WiFi for any existing project with spare UART connection, and it can work both as an Access point(SoftAP mode) and a Station, and even do it simultaneously. Well enough of this, let's get to business..

Getting started

If you just type "esp 8266 getting started" in Google, the first link that comes is this pdf, and trust me, it has all the information that you need to get started. But just a quick analysis of what you will be doing.
You will need to communicate with the ESP8266 through UART, so you'll probably need a USB to serial converter, like one found on the Arduio, or any other FTDI usb-serial converters. You can even use your own if you have one. But before you start poking wires around - keep in mind, ESP8266 works at 3.3 volt, so don't hook it to your 5 volt Arduio. It's best to use a 3.3 volt usb-serial converter, but I didn't have one, what do we do ? Well, it's simple actually, we need few things. At first, a 3.3 volt regulator to power ESP8266. It should be capable to supply at least 200 mA. The more the better. And even if you can't find one, you probably can salvage it from an old motherboard or DVD drive. I got mine from salvaging.
Once you got that, one problem solved, one left - it's inputs are not 5 volt tolerant, so you'll need to logic level converter. May sound complicated, but there are a lot of simple schematics that will work for this case.

I used the zener diode version. It is even used in V-USB projects, but another post for that. The R1 value may need some tweaking, depending on the zener dide used. However, after you have both the voltage regulator and level shifter, we can start poking wires around.

Connections

Look at the pinout in the "getting started" guide, the connections are...
ESP8266 Serial converter
VCC 3.3V
CH_PD 3.3V
UTXD USB-Serial RX
USB-Serial TX 5VTX
URXD To 3.3VRX
GPIO0 To GND when flashing
GND GND
"What about USB-Serial TX?" you may ask. Well, it should consider 3.3 volts a logic one, so no problems there.
Once you got the connections ready, open the USB-Serial converter with a serial terminal program. In my case picocom worked well enough. Things to keep in mind with your serial terminal
  • use CR+LF for line endings
  • default baudrate should be 115200, although it may very
 With picocom it looks like this:
$ picocom -lb 115200 --omap crcrlf /dev/ttyUSB0
Where
  • -l says not to lock (locking needs root permisions)
  • -b sets the baudrate
  • --omap sets line ending to CRLF
Once it's open, the first test to do is writing AT, you should get an OK reply.
If you don't get any reply, or get some gibberish, check the baud rate, and lastly look at getting started guide for info.
Let's assume you did finally manage to get it. Now check the firmware version, type in
AT+GMR
 And you should get a response like the one I got
AT version:0.30.0.0(Jul  3 2015 19:35:49)
SDK version:1.2.0
compile time:Jul  3 2015 20:52:32
OK
The AT verions and SDK version may very. At time that this post was written, the latest version was esp_iot_sdk_v1.2.0_15_07_03, however, you may consider checking for the newest version.

Downloading latest SDK

After scrolling down on the link above, you'll see the attached zip file, download it and extract it somewhere. It contains the latest firmware binaries, that we will flash in next step. Except for the SDK we also will need to tool to flash the ESP8266. One awesome solution for this is the esptool. It's for linux users, but pretty sure you can run it on windows as well, if you try hard enough. Clone the repository where you saved the sdk..
$ git clone https://github.com/themadinventor/esptool.git

Why do this ?

Well few reasons, mine didn't work properly because of some bug in old version. If you find that your's is already the latest version, skip this step.

Flashing the binaries

Note: don't forget to power down, connect GPIO0 to GND, then power up ESP to put it in flash mode.
 esptool is pretty self explanatory. Go to the the extracted sdk folder and find bin folder. In it, at folder contains a readme. It will tell the proper files with proper flash addresses. Mine said:
boot_v1.2+.bin          0x00000
user1.1024.new.2.bin      0x01000
blank.bin               0x7e000 & 0xfe000
 So copy the files listed into the esptool folder, open a terminal there and start writing:
$ python2 esptool.py --port /dev/ttyUSB0 --baud 115200 write_flash 0x00000 boot_v1.2.bin 0x01000 user1.1024.new.2.bin 0x7e000 blank.bin 0xfe000 blank.bin
Where
  • --port tells the serial converter location (e.g. COM2 for windows)
  • --baud tells the baud rate
  • write_flash - well, tells to write to flash
  • then address, file pairs come in, the ones you found in the readme file
Note: for esptool to work you need python2-pyserial installed.
If everything goes planned, you will successfully write the latest firmware.
Connecting...
Erasing flash...
Writing at 0x00000400... (100 %)
Erasing flash...
Writing at 0x00040c00... (100 %)
Erasing flash...
Writing at 0x0007ec00... (100 %)
Erasing flash...
Writing at 0x000fec00... (100 %)

Leaving...

Aftermath

If everything went correct, you got a working ESP8266 with the latest SDK, congratulations. You can check  the version with AT+GMR command. 
You can find AT command list here and here. And a wiki page.
The getting started guide has examples of making TCP/UDP connections and etc. But I suppose, I'll talk about it in the next posts. Bye.

Upcoming posts

Hello! Me again. I managed to get through high school. So, I hope, I will be posting more. A little about upcoming plans / posts... I got few new toys, including an ESP8266, an accelerometer, and a analog magnetic sensor. The last one will be used in a bike speedometer project. So stay tuned for that.

Friday, May 22, 2015

1 wire CRC checking

Well well, pretty long time since the last post, well what can I say, I'm still busy with school. However, recently (like a long time ago), when I was browsing avr-libc package's libraries, I stumbled upon a library, called util/crc16.h, which you may guessed, gives CRC checking utilities.
Now, in 1 wire exploration post, there was a CRC byte, when reading the scratchpad of DS18B20 temperature sensor. If you'd watch the datasheet, you would see that the equivalent polynomial function of the CRC was
CRC = X8 + X5 + X4 + 1
I'm not really sure what that means, but while browsing the library, I noticed a function named _crc_ibutton_update, which seemed to have the same CRC polynomial and was called iButton 8 bit CRC.
There was even an example code, but even without it, the function seems pretty convenient. Meaning that if we would take the code before, and make some changes, it would be:
    one_wire_receive (scratchpad, 9);

    uint8_t CRC = 0;
    for (i=0; i<8; ++i) {
      CRC = _crc_ibutton_update (CRC, scratchpad[i]);
    }

    if (CRC == scratchpad[8]) {
      debug_out ("CRC match!");
    }
    else {
      debug_out ("CRC mismatch :(");
    }

The new code can be found in the source code repository of the blog, under 1_wire_0/1_wire2_CRC.c. That's all for today!

Wednesday, March 18, 2015

BMP180 Barometric sensor, with I2C bus

It's been more than a month since the last post, but anyways. This time around, we're messing around I2C (TWI) or so called Inter-Integrated Circuit (Two wire interface). For the example we'll take whatever the heck I have around, that works with I2C, luckily enough, I have a BMP180 digital pressure sensor (datasheet), with it's breakout board. I saw few around, some had on-board regulators, others not. However, check for your breakout board before connecting. Notice, that because SCL and SDA are open collectors, they in most cases will be 5 volt tolerant. Ok, let's get started..

A little about I2C

If you search for "I2C", you'll end up getting something that looks like this:
I2C is a multi-master, multi-slave, single-ended, serial computer bus invented by Philips Semiconductor, known today as NXP Semiconductors, used for attaching low-speed peripherals to computer motherboards and embedded systems.
Now what is the meaning of this hard words ? Well, at first, it's multi-slave, meaning more than one slave devices can be connected. Same for the multi-master, and single-ended, as for not differential, it's just one wire, that carries voltage over the common wire, and that voltage is representing data.
There are several signals over the bus, so there is the Start, Stop, ACK and NACK, and probably some others that we wouldn't care about right now. So all the transmission over I2C happens with 9 bits - 8 general data bits, and 1 acknowledgement bit. By general data I mean all the information transmitted over the bus, and the ACK bit is used for feedback from Master to Slave, or Slave to Master.
Everything starts with a Master device asking for the bus, as for asking for microphone to talk to all the Slaves .. well it got kindof dark, but hang with me here.. The asking for microphone so to say, is the Start signal. After the start signal, everyone listens to whatever the one with microphone will say. It addresses the desired slave, and it is being done by the special 7 bit address that should be unique for the slave device. After the 7 bit address, there is another bit, representing Read or !Write (1 to read, 0 to write), and as discussed earlier, the last bit is the ACK bit, which the slave device should send back, if it is being addressed. With 7 bit address range there would be 27 possible addresses, which is 128. But there is a broadcast address, 0x00, so 128 - 1 gives 127 possible slave devices on the same data bus.
After addressing the desired slave device (or all of them), the Master starts transmitting data bits, 8 by 8, listening to the ACK bit. The slave device, on it's own will send acknowledgement until it reads the data, once it's done, it will send a NACK, or in other words, won't send the ACK bit. Same goes on when the Master sends a read command, although in that case, Master is one sending ACK or NACK's, and is listening to data bits transmitted by the slave device.
On the hardware level, there are two lines - SDA or data line, and SCL or the clock. The clock line is always being driven by the Master. Both lines are being pulled high with pull up resistors, the value of which is depending on the number of devices, voltage and line capacity. In simple cases 4.7k to 10k would work. All data is transmitted by letting the line high, or grounding it. More info about I2C standard can be found here.

I2C on AVR's

Most of the AVR 8 bit microcontrollers have hardware implementation of I2C or TWI as they call it. It saves a lot of trouble, and allows to get pretty high speeds with it. It should be noted, that all the information to configure and get Two Wire interface rolling on AVRs is in the magical document, so called datasheet. Of course you could save trouble and use already made libraries, but .. whatever, choose yourself. I chose to make a simple library for later projects.
To get rolling, you'll need to know few registers. Names might change from my Atmega16 to your whatever the hell you'd use, but the general algorithm is the same.
  1. Set the TWBR - baud rate register, which is used to configure the clock frequency, together with the TWPS bits
  2. The start signal is done by setting TWSTA, TWEN, TWINT bits in TWCR in Two Wire Control register
  3. The stop signal is done by setting TWSTO, TWEN, TWINT bits in TWCR in Two Wire Control register
  4. You should poll for TWINT bit in TWCR
  5. You should take proper actions to the TWSR
I'll do a more specified tutorial/guide about TWI, if it is really necessary, but I'm pretty sure datasheet has plenty enough info in it already.

BMP 180 - the calculating mathematical hell

On BMP 180 datasheet, page 15, there is the algorithm of doing stuff with this sensor. You begin by reading all the calibrated variables, that at first sight you'd think nobody needs. It is done, by start TWI transmission, sending the address of register that you want to read, restarting transmission, this time with READ address, and reading as much as you want. Don't forget to send NACK after the last byte. Once you have those hellish 11 2 byte variables, some of them signed, few unsigned, you can start the next easy part..
Now, you can either select convert temperature, or convert pressure(page 21). Again, you start TWI transmission, send the control register address (0xF4), and send either convert temp(0x2E) or convert pressure(0x34). The OSS stands for oversampling, which is used to get more accurate data (up to 19 bits). However, for sake of simplicity, we'll do OSS=0, so 0x34 for convert pressure. Both of those processes, take some time. So after sending 0XF4(register address), send once for temperature, once for pressure. Stop TWI transmission, wait ~4.5 ms.
To read the unconverted, or unreal temperature and pressure data, start TWI transmission, send the MSB byte address (0xF6), restart, this time with read address, and read two bytes. At this moment, you should have both unconverted temperature and pressure data, as 16 bit unsigned integers.

The real value calculation ~_~

As you might have noticed, converting the unconverted data to useful information, takes quite some effort (page 15). Well at least this part doesn't take much of explanation, just do as the datasheet says. And after hours of conversation, you'll get unreal numbers, probably because you made few mistakes here and there. Try several other times, I'm sure you'll get it working afterall..
In my case, I did get something, it looked in my debug terminal something like this:

To check, and make sure that your data is somewhat real, use this web page to find altitude at your location, and this web page calculator thingy, to calculate weather using the pressure and temperature, your data is real or not. I got around 1259 m, which is quite about right. All the code part of this project can be found at this blog's bitbucket repository. Thanks for reading! :)

Friday, February 6, 2015

Messing around an Electret microphone

Hello there .. again. It has been a while, so today we will be messing around an electret microphone. The mission for now is : Make a simple voice recorder. Of course we won't be getting studio quality (obviously), and that is .. for several reasons..
  1. ADC on Atmega16(the one that I got around) gives tops 15 kilosamples per second, and it's just 10 bits. While just CD quality requires 16 bit, 44100 kilosamples.
  2. Except for getting ADC values, we also need to send it somehow to the computer
  3. ... well you got the idea, whatever, moving on
BUT, we can still mess around, see the best we can do with minimal effort possible.

The action plan ..

So, the name electret comes from two words - electrostatic and magnet (wiki). It basically has two conducting plates, one of which is serving as a membrane, and when mechanical waves (i.e. sound) hits the membrane plate, it causes change of capacity, which will cause a change in voltage. Mostly all this type of microphones have a FET inside for gain, so all you would need to do is bias it with a resistor. For my case I used a 1k resistor.

So, we biased it, then what ? Then will just hook it onto one of Atmega16's differential input pins (Port A 3 - in my case) through a capacitor (~50 nF), and the negative pin of the differential input will be connected to the ground. Also, we could really use some gain.
Well, 10x sounds tempting, let's see. Ok, we have the idea of inputs, now sampling rate and resolution.. AVR's ADC offers us up to 10 bit resolution. Sending 10 bits to the computer .. what a drag.. if it would have been 8 bits, we could send it over to the computer using UART, seems like the most effortless way. Sure, we will loose those two bits .. but whatever, too much effort, not worth it. As for sampling rate, the important information when speaking is contained around 1-4 khz, so I'd guess 10,000 samples per second sounds pretty good. Assemble the schematic on your breadboard, and lets move on to the code.

Code

Once again, what we decided:
  • 10 kilosmaples per second
  • 8 bit resolution (ADLAR :)
  • UART feedback to the computer
  • Differential input channels
I guess the Free running mode for ADC was made just for this purposes. The clock for my MCU is 16 mhz. Divide it on 10,000, you'll get 1600. So if we divide our clock (clock_io) on that magical number - 1600, we'll get our sampling clock. 1600 = 8 * 200. Thus, if we choose the clock divider to be 1 : 8, we can fit in the 8 bit timer counter 0. Configure it on CTC mode to count from 0 to 199 (200 ticks), on 16 mhz / 8 frequency, the sampling clock is done. Now how to trigger the ADC on this event ? Well, several ways for this. The one that comes to mind - just make it start the conversion from the interrupt of Timer 0 Compare match - but that's a bad idea, we have free running mode made for this. So by configuring the ADC, we'll just say it to look up for Timer0 compare match flag, and when it gets set, start converting. When done, it'll call the interrupt handler for ADC finished, and we'll just send the byte of information out from the interrupt. CRITICAL: in order for ADC to start conversion, the selected interrupt flag should go from 0 to 1. So if you don't clear it, it will get stuck. If you enable hardware interrupt for current interrupt flag, it will clear the flag itself, but however, if you don't, just write logical one to the bit spot of that interrupt flag.
So, the function to start the Timer counter 0, pretty simple actually.
void init_timer0 (void) {
  /* 
   * 8 bit timer counter 0
   * Mode of operation: CTC
   * Divider: 8 / OCR - 199 (200 divider)
   * Interrupt frequency: 10000 hertz
   */
  TCCR0 = _BV(WGM01) | _BV(CS01);
  // TIMSK |= _BV(OCIE0);
  OCR0 = 199;
}
As for ADC initialization code, we set ADLAR (left adjust, for 8 bit resolution), highest possible clock speed, ADATE (automatic update trigger .. or whatever it was), and MUXes, for selected pins, at selected 10x gain. Also in SFIOR, we select the trigger for ADC to be Timer0 compare match.
void adc_init (uint8_t muxx) {
  SFIOR |= _BV(ADTS1) | _BV(ADTS0); // TIMER0 compare match
  ADMUX =  _BV(REFS0) | _BV(ADLAR)| muxx;
  ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADIE) | _BV(ADATE)
    /* | _BV(ADPS1) */;
}
The interrupt handler in our design had two jobs - send data over UART, and clear the Timer0 compare match flag.
ISR (ADC_vect) {
  // send converted ADC data over uart
  UDR = ADCH;
  // clear TIMER0 compare match flag
  TIFR |= _BV(OCF0);
}
And the main routine - just call all the init functions, and sit back, and relax.

Processing the audio

Well, good, we opened the serial terminal with logging, and got bunch of crap that seems to be changing when we talk. What next ? What we get is not just useless code, we get code, representing our analog signal, or otherwise called raw data. I found that Audacity has a feature of importing raw data.

The resulting wave looked like this.
In case of any questions, comments are welcome. And all the code can be found here. Goodbye!

Tuesday, January 13, 2015

1 Wire Interface library .. design suggestions ?

Well well, hello there again. It has been quite some time, unfortunately the schools started again, so I won't have much time to post. I'll try to make this a quick one.
I once said I should write a 1 wire library, to avoid rewriting code, and make coding more fun and productive .. and whatever. So, I have got the finished "thermometer" project with 3x8 segment displays. And I should admit, it work's quite nice indeed :)
In the process of designing the library, the most important part was choosing the way of how it all will come together. For instance, in my design I chose to have several rather "repeating" functions, but they would shorten the time of writing, and they would be more comfortable, so heck whatever. In that design I had..
Simple functions
  • one_wire_reset - the reset presence pulse thingy
  • one_wire_read - read one byte
  • one_wire_write - write one byte
And some advanced functions
  • one_wire_receive - to receive given amount of bytes into an array
  • one_wire_send_data - send bytes from given array
  • one_wire_send_package - send reset/presence, ROM command, and function command, with arguments
  • one_wire_send - send reset/presence, ROM command and function command, but without arguments
So far this was all the ideas I had in my mind. Btw, I'd appreciate any ideas or comments about the design.. The library is under WTFPL, and kindof made to be easy configurable. The only things that needs to be changed are _1_WIRE_PORT_L and _1_WIRE_PORT_N, which correspond to which PORT's which bit is going to be used as 1 wire interface. Whatsoever, it's easy enough to figure out. And here is the link to the library, the test can be found in this blog's bitbucket's repository. And that was all for today, so long!

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!