Sunday, June 24, 2012

Pull-up Resistors for Open Collector Outputs

As I mentioned in a previous post about twos complement conversion, I am trying to get an SD card to communicate with a micro-controller.  To do this I needed to interface the 5 V logic levels of the micro-controller with the 3.3 V used by the SD card.  My first thought was to swap out the 5 V regulator on the development board, drop in a 3.3 V version and directly connect the SD card to the micro-controller, but the LCD connected to the board needs 5 V to work.  So that just wasn't going to work.  Besides, the version of the ATmega128 I'm using needs at least 4.5 V.  So my only option was to buy or build a voltage level translator to let the 3.3 V SD card talk to the 5 V micro-controller over the SPI bus.  I wanted it done that day so I decided to roll my own.  I know that there are better ways to do this but I was constrained by what Jaycar, my local electronics store, had in stock.  Below I'll go into detail about a problem related to pull-up resistors that I encountered along the way.

Data logger Prototype

Above is an image of my data logger prototype.  It definitely wont pass any EMC tests.  Actually I'm surprised that its emissions aren't opening garage doors around the neighbourhood.  In the bottom left you can see the breakout board that contains the temperature sensor, the blue board is the ATMega128 development board, and the strip board contains the voltage translation circuitry and an SD breakout board from Sparkfun.  The LCD is connected to the dev board via 12 inches of ribbon cable followed by 12 inches of jumper cables.  Yeah, I'm surprised it works too.

Now on to the voltage translator.  Ignoring the power supply lines, the SPI bus requires 4 data lines, 3 controlled by the master, MOSI, CS, and SCLK, and 1 controlled by the slave, MISO.  To translate the 5 V outputs of the micro-controller to 3.3 V I used 3 buffers of the CD4050.  When powered by 3.3 V the CD40450 inputs tolerate 5 V and recognise them as logic high, a logic high based on the 3.3 V supply voltage of the chip then appears on the output.  Pretty basic stuff.

To convert the single 3.3 V output line of the SD Card to 5 V was a little harder as all that was available to me was the SN7407.  The SN7407 is a hex buffer with open collector outputs that allow it to control voltages up to 30 V.  As the outputs are open collectors, pull-up resistors are required for them to function.  I threw in a 10k resistor and hoped for the best.  It worked first go.  The card initialised and a text file was written to the SD card.  Technically I could directly connect the SD card output to the micro-controller as it only requries 3 V on an input to detect a logic high.  However that leaves a range of 3 V to 3.3 V to register a logic high, and that seems like it could be a little slim.  I'm pretty sure I could get away with it, but just to be safe I decided to translate the output voltage while testing.

When an SD card is first initialised, the SPI clock is set at a low frequency, in my case 250 kHz.  Once the card is initialised, the master is able to increase the clock rate based upon data received in the initial exchange.  For testing I chose to leave my clock rate at 250kHz after this exchange, but now things were working I changed my code so the clock would increase to a 1 MHz after the initialisation.  Once again it worked.  Now I decided to push my luck and increase the clock rate to 4 MHz after initialisation.  This time however my luck ran out.  I had seen a lot of other projects where the clock was higher than 4Mhz and there wasn't a problem, so why wasn't mine working?  As this is a data logger it didn't really matter.  I could leave the clock rate at the low frequency and it'd be fine.  However, curiosity got the better of me and decided to have a quick look at the problem to see if I could fix it, if not, I could move on and be content with the low clock rate.

I thought I knew exactly what was wrong and started to trouble shoot that part of the circuit.  My suspicion was that the 10k pull up resistor on the output of the SN7407 was too large and the MISO signal from the SD card was being distorted.  While probing the SN7407 output I set the clock rate at 250kHz, 1MHz, and finally 4MHz.  I was unable to detect any signal for the 4 MHz clock rate.  Screen shots of my scope are below.

SPI clock rate at 250 kHz, R pull-up 10k

SPI clock rate at 1 MHz, R pull-up 10k

After looking at the scope output it became obvious what the problem was.  The rise time on the output of the SN7407 was approximately 700 nS which was just enough for operation at 1 MHz, but it wasn't going to cut it at 4 MHz.  Notice that the fall time of both signals is comparatively non-existent.  All of these effects can be explained by taking a look at a simplified representation of a channel on the SN7407.

Simplified view of the SN7404 connected to a pull up resistor.

To understand what's happening we need to recognise that there's capacitance on the output, and it needs to be charged and discharged every time the output changes state.  Capacitance on the input of the next stage, along with inherent capacitance in the output transistor of the SN74074, and capacitance of the traces of wire connecting the two stages are all combined and represented by the capacitor Co.

First we assume that the device is fed a low input and has been in this state for some time.  This low input is inverted to give a high at Va.  This high turns on the output transistor and causes a current to flow through the pull-up resistor which drives the output voltage low. As the transistor has been in this state for some time, the capacitor is drained and has stabilised to the low output voltage.  If the input is now set to high, the voltage at Va becomes low and the transistor turns off.  This effectively disconnects the collector from the pull up resistor and output capacitance.   This leaves a simple RC circuit where the output capacitor is slowly charged to 5 V via the pull up resistor.  The time taken to reach 63% of the final output is equal to RC.  By looking at the scope output we can calculate that a capacitance of approximately 30pF is being charged via the 10k pull-up resistor.

After enough time passes and the voltage across the output has stabilised, the input is now driven low again.  This turns the transistor back on and causes current to flow through the collector and discharges the output capacitance.  Whereas the time to charge the capacitor was controlled by the high impedance pull-up resistor, the time to discharge the capacitor is controlled by the low impedance path through the transistor.  Because this occurs a lot faster, the fall time is small compared to the rise time.

To decrease the rise time the output capacitance needs to be charged faster, and the easiest way to do this is to drop the value of the pull up resistor.  By replacing the 10k resistor with a 1k resistor, the rise time should decrease by a factor of 10.  So that's exactly what I did.  Using clock rates of 250 kHz, 1 MHz, and 4 MHz I again tried to write to the SD card.  This time however the system worked using the 4 MHz clock rate, and if we once again probe the output signal we can see why.

SPI clock rate at 250 kHz, R pull-up 1k
SPI clock rate at 1 MHz, R pull-up 1k
SPI clock rate at 4 MHz, R pull-up 1k

Using the scope we can see that the rise time of the pulses is now around 70 nS, which is 10% of what it used to be.  Problem solved. There is a trade off to consider however.  Using a smaller pull-down resistor will use more power, but as I am powering this of plug pack I don't have a power budget to consider.  Don't get me wrong, I'm not going out of my way to waste power, but it's not my biggest concern.

By looking at this simple problem you can see how something as basic as a pull up resistor can effect the operation of a circuit, and although I have the SPI bus working with a clock rate of 4MHz I'll only run it at 250kHz.  If it works at 4MHz it should be rock steady at one sixteenth the speed.  It's only a data logger after all.

Monday, June 18, 2012

Two's Complement Conversion in a Microcontroller

Recently I've been working on a project where I use a micro-controller to take temperature measurements and then log the data to an SD card.  So essentially it's a data logger using an ATMEGA128, a little bit of overkill, but I already have a dev board running that chip at 16MHz, so it's ideal to hit the ground running.

Getting the SD card up and running is a bit problematic but I'm making headway.  I have however managed to get the temperature sensor connected to the micro-controller via a bit banged I2C interface using a library from Peter Fleury's Site.  The temperature sensor in question is the ADT75 from Analog Devices.  I don't plan to use this sensor in the final design because it isn't accurate enough, but I already had it mounted on a breakout board for a project I did at Uni.  So for now I can get things up and going and at a later point change the sensor.

ADT75 temperature sensor in operation


The ADT75 measures from -55 °C to 125 °C with a temperature resolution of 0.0625 °C.  The return data from the sensor is sent as a two's complement formatted two byte sequence.  As each measurement is only 12 bits, the last 4 bits of the last byte are always zero and are ignored.  It would be easy to just log the hex code from the sensor to the file, but to make things more user friendly I plan to record the hex code and the human readable format.  During testing I can also output this data to the 16x2 character LCD I have connected, once again via a library from Peter Fleury's Site.

To convert from the two's complement format to a human readable decimal format is a relatively simple process. The first step is to test if the number is negative.  The most significant bit of the 16 bit temperature reading will be 1 if the reading is negative.  This is tested by masking the reading with 0x8000.

positive reading
  0xxxxxxx xxxx0000
& 10000000 00000000
= 00000000 00000000

negative reading
  1xxxxxxx xxxx0000
& 10000000 00000000
= 10000000 00000000

If a negative measurement is detected a negative symbol is added to a character buffer, and the measurement is then negated.  This is done by toggling the bits in the reading by XORing them with 0xFFF0 and then adding 1.  It is important to remember that the 4 least significant bits are to be ignored, therefore we actually add 0x0010.

  1xxxxxxx xxxx0000
^ 11111111 11110000
= 0yyyyyyy yyyy0000
+ 00000000 00010000
= 0zzzzzzz zzzz0000

Any negative measurements will be now be positive.  The next step is to convert the positive readings to a decimal representation.  The 8 most significant bits will now contain the integer part of the temperature measurement and can be converted to a string and added to the character buffer followed by a decimal point. The fractional part of the reading is now contained in the the upper 4 bits of the lower byte.  There are a couple of ways to convert this to decimal, you could use maths functions, or use a look up table as I am going to do.  The first step is to mask off the required bits with 0x00FF and shift them to the lower 4 bits.

  0zzzzzzz zzzz0000
& 00000000 11111111
= 00000000 zzzz0000
>>4
= 00000000 0000zzzz


We are now left with a number between 0 and 15.  As this is the fractional part of the reading, it represents how many 16th make up the numbers after the decimal point.  The decimal representation can be calculated by multiplying this number by one sixteenth, which is 0.0625.  However, as there are only 16 different possibilities it's more efficient to pre-calculate them and store them in memory.  It will soon become apparent why, but we are lucky the decimal values are all able to be represented with 4 digits.  The string below contains the 16 different 4 digit decimal sequences.  Each sequence is marked by the ^ symbol.

0000062512501875250031253750437550005625625068757500812587509375
^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   ^


For example if the value is 7, the fraction to calculate is 7/16.  To retrieve the answer go the the eighth sequence and read the next 4 characters.  We go to the eighth sequence because zero is included.  For a zero indexed array this sequence will start at the position 7*4 = 16. The string returned will be 4375 giving an answer of 0.4375.  These digits can then be added to the character buffer, followed by a null terminator.

We can take a short-cut to calculate the index position.  The position in the array is found by multiplying the fraction required by 4, but in the previous step we did a right shift by 4 bits which is equivalent to division by 16, by combining these two steps we can replace the right shift by 4 with a right shift by 2.

index position
  0zzzzzzz zzzz0000
& 00000000 11111111
= 00000000 zzzz0000
>>2
= 00000000 00zzzz00


My function to complete this operation is listed below.  It takes a pointer to a uint16_t temperature measurement and a pointer to a character buffer where the result can be stored.  It is important to make sure that buffer is large enough to hold the result otherwise a buffer overflow can occur.  A buffer 9 characters long should be enough to hold the conversion.

Negative Value
-xx.xxxx(NULL)

Positive Value
xxx.xxxx(NULL)


void binaryToDecimal(uint16_t * Temp, unsigned char * tempString){
    static const char mantissaLookup[64]  PROGMEM = {
        '0','0','0','0',
        '0','6','2','5',
        '1','2','5','0',
        '1','8','7','5',
        '2','5','0','0',
        '3','1','2','5',
        '3','7','5','0',
        '4','3','7','5',
        '5','0','0','0',
        '5','6','2','5',
        '6','2','5','0',
        '6','8','7','5',
        '7','5','0','0',
        '8','1','2','5',
        '8','7','5','0',
        '9','3','7','5'};    //lookup string for mantissa of multiples of (1/16)

    uint16_t  temperature = *Temp;                    //pointer to temperature variable
    unsigned char buffer[16];                        //temporary buffer for string conversion
    uint8_t stringIndex = 0x00;                        //string position

    if(temperature & 0x8000){                        //test if the temperature reading (2's complement) is negative 
        temperature ^= 0xFFF0;                        //invert bits in reading.  The 4 LSBits are to be ignored
        temperature += 0x10;                        //add 1 - negative readings now turned positive. The 4 LSB are to be ignored
        tempString[stringIndex] = '-';                //add negative sign to the output string 
        stringIndex++;                                //increment the position in the string
    }

    utoa((temperature >> 8),buffer,10);              //take the top 8 bits of the reading (integer part) and convert to a string
    uint8_t bufpos = 0;                              //buffer position
    while(buffer[bufpos] != '\0'){                   //while the null character is not reached continue
        tempString[stringIndex] = buffer[bufpos];    //add the character from the buffer to the temperature string
        bufpos++;                                    //increment the buffer position
        stringIndex++;                               //increment the position in the string
    }

    uint16_t mantissa;                               //variable to hold the mantissa of the reading
    mantissa = ((temperature & 0x00FF) >> 2);        //mantissa is equal to (x/16), where x is held in the bits 4 highest bits of the LSByte
                                                     //to get this value, mask off the LSByte and divide by 16.
                                                     //This value is then multiplied by 4 to get the position of the digits in the lookup table
                                                     // (a>>4)<<2 = a>>2

    tempString[stringIndex] = '.';                                                    //add the decimal point to the output string
    stringIndex++;                                                                    //increment the position in the string

    tempString[stringIndex] = pgm_read_byte_near(mantissaLookup + mantissa + 0);    //put digit 1 of the mantissa in the output string
    stringIndex++;                                                                    //increment the position in the string
    tempString[stringIndex] = pgm_read_byte_near(mantissaLookup + mantissa + 1);    //put digit 2 of the mantissa in the output string
    stringIndex++;                                                                    //increment the position in the string
    tempString[stringIndex] = pgm_read_byte_near(mantissaLookup + mantissa + 2);    //put digit 3 of the mantissa in the output string
    stringIndex++;                                                                    //increment the position in the string
    tempString[stringIndex] = pgm_read_byte_near(mantissaLookup + mantissa + 3);    //put digit 4 of the mantissa in the output string
    stringIndex++;                                                                    //increment the position in the string
    tempString[stringIndex] = '\0';                                                    //add the null terminator character to the output string
}

When testing the temperature sensor I wanted to make sure that it was measuring and converting negative temperature readings.  To do this I used common electronics trick.  If you take a standard can of pressurised air that is used to clean keyboards and use it upside down, the propellant comes out.  There is a warning not to do this on the can because the propellant is very cold and could cause injuries.  We can use this to our advantage and give the temperature sensor a quick blast of the propellant to take its temperature below zero.  First make sure that there are no sources of ignition as the propellant is flammable.

The ADT75 measuring a negative temperature

Tuesday, June 5, 2012

Using Xournal to Annotate PDF Presentations

When doing screen-casts I sometimes use PDF documents as the basis of my presentation.  To do this I need to be able to show the document full screen and easily move between pages.  Althought the standard PDF viewers in Linux don't quite do what I want, Xournal does the job brilliantly.  Although I use it for screen-casts it would work just as well in a lecture situation.

While doing presentations you can annotate PDF files and save the output to another PDF file.  I have nightmares about lectures using OHP's where the lecturer spent ages trying to get a pen that worked and then you couldn't concentrate on what they were saying because you were too busy copying notes.  Using a combination of Xournal and PDF documents you can streamline the process.  Have your presentation done before hand, if you feel the need to clarify a concept or add other relevant notes you can do that while you're presenting, at the end save the file and upload it to the internet.  This allows people viewing your presentation to focus on what you're saying instead of madly copying notes.  The process is so simple and fast that a student could walk out of a lecture and immediately retrieve the file for printing or viewing.  All that's required to do this is a computer and preferably a graphics tablet, although in a pinch a mouse could be used.

I know programs like this have been around for some time, but this is the nicest free one that I have used, so I thought I'd do a quick demo on how to use and configure Xournal.