Thursday, October 16, 2014

The Difference Between an Amateur Engineer and a Professional Engineer

An amateur grabs any old part from the parts bin and puts it into the board. A professional only uses parts that he has a valid part number for, since it will need to go into documentation somewhere.
An amateur assembles boards themselves. A professional only hand-builds if it will save money.
An amateur stores board revisions in zip files. A professional uses version control (like SVN).
An amateur doesn't pay attention to ESD when reworking boards. A professional uses proper grounding & wrist strap.
An amateur tries to solve all the problems himself. A professional coerces the manufacturer to help. :)
An amateur tests whether a design works. A professional tests how well the design works.
An amateur cares about the cost of the software tool. A professional cares about the productivity of the tool.
An amateur asks a question on a forum without RTFM. A professional asks a question on a forum after RTFM, usually when the documentation contradicts itself. :)
An amateur doesn't care about software licensing. A professional can tell you when you can use GPL code vs. LGPL vs. BSD license.

Friday, September 26, 2014

Demystifying Decoupling Capacitor Placement

Digital ICs require decoupling capacitors, to absorb the small current spikes from the switching behavior of the IC. Most of what we know about decoupling capacitor layout is "conventional wisdom", such as:

1. Need a variety of capacitance values to decouple a wide frequency range
2. Many capacitors of one value is better than many values
3. Place caps close to ICs
4. Location doesn't matter
5. Spread caps across the entire board

Most of our conventional wisdom is based on guidelines instead of empirical data.

I found an excellent presentation which explains proper decoupling capacitor placement:
http://www.pcbcarolina.com/images/01_pcb_power_decoupling_myths_debunked.pdf

Friday, August 1, 2014

Using Texas Instruments Tiva Microcontroller Temperature Sensor

The Tiva line of processors from Texas Instruments has an internal temperature sensor. Code to do this is below. To use this,
  1. Call halTemperatureSensorInit() to initialize the sensor and ADC. Only needs to be done once.
  2. Call halGetTemperature() to get the temperature in Celsius
  3. Optionally, call halConvertTemperatureFromCtoF to convert it to Fahrenheit
Example code is below.

/**
* Tiva Microcontroller Internal Temperature Utility Functions
*
* @section license License
* This work is PUBLIC DOMAIN
*
* YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE PROVIDED “AS IS”
* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY
* WARRANTY OF MERCHANTABILITY, TITLE, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO
* EVENT SHALL  TESLA CONTROLS BE LIABLE OR OBLIGATED UNDER CONTRACT, NEGLIGENCE,
* STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER LEGAL EQUITABLE THEORY ANY DIRECT OR
* INDIRECT DAMAGES OR EXPENSE INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT,
* PUNITIVE OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT OF SUBSTITUTE
* GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY
* DEFENSE THEREOF), OR OTHER SIMILAR COSTS.
*/

/**
 * Initialize the ADC for use by the temperature sensor.
 * @see TivaWare/examples/peripherals/adc/temperature_sensor.c
 * @post temperature sensor can be read with halGetTemperature()
 */
static void halTemperatureSensorInit()
{
    /** Enable the ADC */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC0);

#define ADC_SEQUENCE    3
#define ADC_STEP        0

    /* Enable sample sequence 3 with a processor signal trigger.
     * Sequence 3 will do a single sample when the processor sends a signal to start the conversion. */
    ADCSequenceConfigure(ADC0_BASE, ADC_SEQUENCE, ADC_TRIGGER_PROCESSOR, 0);

    /* Configure step 0 on sequence 3:
     * - Sample the temperature sensor (ADC_CTL_TS) in single-ended mode (default)
     * - Configure the interrupt flag (ADC_CTL_IE) to be set when the sample is done
     * - Tell the ADC logic that this is the last conversion on sequence 3 (ADC_CTL_END). */
    ADCSequenceStepConfigure(ADC0_BASE, ADC_SEQUENCE, ADC_STEP, ADC_CTL_TS | ADC_CTL_IE | ADC_CTL_END);

    /* Enable the ADC Sequence we're using */
    ADCSequenceEnable(ADC0_BASE, ADC_SEQUENCE);

    /* Clear the interrupt status flag. This is done to make sure the interrupt flag is cleared before we sample. */
    ADCIntClear(ADC0_BASE, ADC_SEQUENCE);
}


/**
 * Read the processor's internal temperature sensor. Accuracy is +/-5C
 * @pre halTemperatureSensorInit() was called to initialize the ADC
 * @return temperature of processor in degrees Celsius
 * @note to convert: VTSENS = 2.7 - ((TEMP + 55) / 75)
 */
int32_t halGetTemperature()
{
    /* This array is used for storing the data read from the ADC FIFO. It must be as large as the
    FIFO for the sequencer in use.  This example uses sequence 3 which has a FIFO depth of 1. */
    uint32_t ulADC0_Value[1];

    /* Our output value */
    int32_t temperatureInDegreesC;

    /* Manually Trigger the ADC conversion */
    ADCProcessorTrigger(ADC0_BASE, 3);

    /* Wait for conversion to be completed */
    while(!ADCIntStatus(ADC0_BASE, 3, false))
    {
    }

    /* Clear the ADC interrupt flag. */
    ADCIntClear(ADC0_BASE, 3);

    /* Read ADC Value. */
    ADCSequenceDataGet(ADC0_BASE, 3, ulADC0_Value);

    //UARTprintf("ADC Value = %u\n", ulADC0_Value[0]);

    /* Reference voltage of 3.3V, in mV.  */
#define REFERENCE_VOLTAGE_MV                (3300l)

    /* ADC is 12 bit resolution */
#define NUMBER_OF_STEPS_12_BIT_RESOLUTION   (4096l)

    /* The voltage from the ADC, in millivolts */
    uint32_t adcMv = (uint32_t) ((ulADC0_Value[0] * REFERENCE_VOLTAGE_MV) / NUMBER_OF_STEPS_12_BIT_RESOLUTION);

    /* Use non-calibrated conversion provided in the data sheet. Divide last to avoid dropout. */
    temperatureInDegreesC = (147500l - 75l * adcMv) / 1000;

    return temperatureInDegreesC;
}


/**
 * Utility function to convert a celsius temperature to fahrenheit.
 * @param temperatureInDegreesC the temp in C
 * @return the temp in F
 */
int32_t halConvertTemperatureFromCtoF(int32_t temperatureInDegreesC)
{
    return ((temperatureInDegreesC * 9) + 160) / 5;
}

Thursday, May 8, 2014

The Main Event - A Simple Event Handler for Microcontrollers

Every application has some type of event handler. Typically, an interrupt will occur, and the interrupt service routine (ISR) will then set a flag, and then the ISR will exit to return to the normal application. This approach is used to minimize the time spent in the ISR. Next, in the main application, the event flags are read, and if any were set then the application processes each event.

I've been looking through the Anaren Bluetooth Low Energy software lately, and I saw this rather cool event handler. It uses a table of function pointers that are the functions to be called when the event occurs.

The Event Handler Functions

We will create a typedef for our function pointer. All functions in our Event Handler Table need to be this type.
/* Typedef for the event handler functions that will go into our table */
typedef void (*Hal_Handler)(void);

Next, for convenience we also create a type for the event flag bitmask. For speed, make this the same as the processor's data size.
/* The type of integer that we will use for our event bitflag. uint32 = 32 possible events */
typedef uint32_t EventFlags;

We also create the global variable that will be the bitflag of any events that get set.
/* Bitflag of the events that need to be processed */
static volatile EventFlags handlerEvents = 0;

To complete the initialization, we define a few events, and create our event handler table.
#define NUM_HANDLERS 3
#define BUTTON_HANDLER_ID      0
#define TICK_HANDLER_ID        1
#define DISPATCH_HANDLER_ID    2

#define KNOB_INCREMENT_HANDLER_ID  3
#define KNOB_DECREMENT_HANDLER_ID  4

/* Table of handler function pointers */
static Event_Handler handlerTable[NUM_HANDLERS];

This table will contain function pointers for the function that gets called when the corresponding event gets called. For example, when the knob gets rotated clockwise, its ISR will set bit #3 in the handlerEvents function. Then the main loop will call the function that is located at handlerTable[3].

We initialize our list by adding event handlers to the event handler table using the method below:
 int addEventHandler(uint8_t index, Event_Handler handler)
 {
     if (index > NUM_HANDLERS)
     {
         return -1;
     }
     handlerTable[index] = handler;
     return 0;
 }

We then call this in our main() function, which we'll explain in a bit.

Set a flag when an event occurs

When an event happens, in the ISR we need to set the bitflag that corresponds to that event. This is done in the postEvent() method as shown below. This bitflag will be read later in our main event handling loop. For safety, we disable interrupts while we are modifying the handlerEvents variable.
/** Set the corresponding bitflag for the eventId */
void postEvent(uint8_t handlerId)
{
    //Note: you should check that handlerId < NUM_HANDLERS here
    IntMasterDisable();
    handlerEvents |= 1 << handlerId;
    IntMasterEnable();
}

In the ISRs, you will need to add a call to postEvent. For example, in a button ISR, add:
postEvent(BUTTON_HANDLER_ID);

Configuring The Actions

When the bitflag for an event gets set, we want the event handler to do something. It does so by calling a function pointer that points to the function that we want to get called when that event happens. For example, when a button is pressed we just want to tell the user. Same for knob, as shown below:
void buttonHandler(void)
{
    printf("Button Pressed!\r\n");
}
void knobIncrementHandler(void)
{
    printf("Knob Up!\r\n");
}
void knobDecrementHandler(void)
{
    printf("Knob Down!\r\n");
}

Now, we need these in the Event Table so in main() we add them with the function we created earlier:

    addEventHandler(BUTTON_HANDLER_ID, buttonHandler);
    addEventHandler(KNOB_INCREMENT_HANDLER_ID, knobIncrementHandler);
    addEventHandler(KNOB_DECREMENT_HANDLER_ID, knobDecrementHandler);

Now we've configured the event setting, and the event processing, so now we can implement the main event handler.

The Main Event (handler)

The Main Event Handler is fairly simple. It continually loops, and processes any events that were set by postEvent(). If a bitflag was set, then it calls the corresponding function pointer.
void eventHandler(void)
{
    IntMasterEnable();
    for (;;)
    {
        /* First, disable interrupts so we don't get messed up while we are reading events */
        IntMasterDisable();

        /* While interrupts are disabled, copy the current list of events that need to be processed */
        EventFlags events = handlerEvents;

        /* ... and clear the master bitflag of events */
        handlerEvents = 0;

        /* Now, process any events that need to be processed */
        if (events)
        {   // dispatch all current events
            IntMasterEnable();     // Enable interrupts - note that we do this before calling each handler function
            /* mask will be the bitflag that we use to check the events bitflag. Starts at 0x1, then 0x2, 0x4, 0x8 etc. */
            uint16_t mask;
            /* The numeric index of the event - 0 through 31 */
            uint8_t id;
            /** Iterate through all possible events */
            for (id = 0, mask = 0x1; id < NUM_HANDLERS; id++, mask <<= 1)
            {
                if ((events & mask) && handlerTable[id]) // If we need to process the event
                {                                      // AND there is an event handler for it
                    handlerTable[id]();                  // ... Then process the event
                }
            }
        } else {          // No events to handle, so wait for more
            /* If using a battery powered device, you could go back to sleep here. Just be sure to enable interrupts! */
            IntMasterEnable();        // Enable Interrupts
        }
    }
}





Friday, May 2, 2014

Using Bluetooth Low Energy to Do Something


In the previous post we looked at how to get up and running with Bluetooth Low Energy. Now we're going to do something useful. This section assumes that you have completed the previous tutorial successfully.

Load on-line Help

First, we want to load the on-line help for the FirstApp example. Open Em-Builder and select Help > Em-Builder > Primer Contents and you'll see a number of lessons available:
 Select "Lesson 01 - Your First App" and the workspace will change to load the on-line help:

Run Unmodified FirstApp

Just like you did for Blinker, now right-click on the FirstApp project in the left hand column and select "Run Em-Builder Example". If you had an error, be sure that your development board is attached, and try the Em-blinker example too. Now open Em-Browser on your iPhone and connect to your development board. You should be able to read and write a variable with the unoriginal name of "data". Fun.

Customizing FirstApp

The first thing that we're going to do to get our feet wet with the Emmoco software is to do something very simple: change the name of the "data" variable to "pcbTemperature". This will give us a good exposure to what Emmoco uses to communicate. Make the change in the schema, FirstApp.ems.


 If you try to build it, then you will get an error, complaining of an unknown type name.

Now in the schema (FirstApp.ems) change the name of the variable "data". This will cause the build to fail, because we need to change all the other variables. If you go to FirstApp.h then you can see declarations of the new typedef and functions.
Now, make these changes to FirstApp.c
Now that we've renamed the variables, we should be able to build successfully. Run the example (Right-click on project and select "Run Em-Builder Example") and it will be loaded on the development board. Using Em-Browser you should be able to read and write a resource named pcbTemperature.

Do Something When We Receive a New Value

Ok, now we're rolling.

Now, just for fun, we modify the function pcbTemperatureC_store() as shown below to blink LED twice whenever we get a new value.
Now, in Em-Browser, write a value to pcbTemperature and you'll see the LED blink twice. Cool! Too bad we don't have printf() here though; it would be nice to be able to see it.

Adding Another Variable

Now we want to add another variable. I looked through the schema examples and saw that there's a 'num' type where you can set the range and interval. This is useful so that the phone application will only send us valid values. We want to control a light, so we add a variable, called 'level'. This can vary between 0% (totally off) and 100% (totally on), in steps of 1 percent each.
Now right click on the project and select"Build Project". It will report a few errors but more importantly it will automatically generate the typedefs and function declarations that we need. This will make our lives easier.
Here we can see that the Emmoco framework created a bunch of new stuff for us, including the following.
The min, max, step, and scale values are there because in the schema we defined our variable as a number with range of 0 to 100 and in steps of 1. We can copy/paste the function declarations into our application to make life easier. Now we need to add these to our application.

In FirstApp-Prog.c, add a variable:
static FirstApp_level_t intensity = 10;
Also add two new functions:
/* Send resource TO phone */
void FirstApp_level_fetch(FirstApp_level_t* output)
{
    *output = intensity;
}

/* Receive new value FROM phone */
void FirstApp_level_store(FirstApp_level_t* input)
{
    intensity = *input;
}

Run the Example

Now, try building again - 'Run Em-Builder Example'. You shouldn't have any errors.
Now open Em-Browser on the iPhone. You can now see that we have a new resource, called Level, and we can write to it and read it.
Clicking on 'level' shows us more information about it. Note that if you try to write a level above 100 then it will be limited to 100.


Conclusion

In this tutorial we demonstrated how to modify one of their examples to do something useful and how to add another variable.








Get Started with Bluetooth Low Energy

This tutorial will show you how to develop an application with Bluetooth Low Energy and do something useful. Going through all the steps in this post will take between 30-60 minutes, depending on if you run into any issues. Bluetooth Low Energy (aka BLE) is all the rage right now, and rightfully so, as it is an easy to use low power RF communications protocol. All the new iPhones and some Android phones incorporate a BLE radio, so you can now easily design RF peripherals to communicate with the iPhone.

I like to use a module for BLE because it is easier and less expensive for quantities less than 10k. For this post I'm trying out the Anaren BLE Module which is available on a Development kit from Digikey for $60. You will also need a Texas Instruments Launchpad. I'm using the TI Tiva Launchpad which available from Digikey for about $14. These are so cheap that I recommend buying two. Below is a picture of the Anaren Board mounted on the Tiva Development Board.
 For this tutorial, you will need:
  • Anaren BLE Development Kit
  • Mini-USB cable for above (comes with the kit)
  • Texas Instruments Tiva Launchpad
  • Micro-USB cable for above (comes with the kit)
  • iPhone, 4S or later to run phone examples
  • iPhone apps "Em-Blinker" & "Em-Browser", available for free from the App Store
  • Basic knowledge of C

Hardware Connections

First, mate the two boards as shown above. Next, connect the Tiva's "Debug" USB port to your computer. Be sure that the slider switch is in the "DEBUG" position. Finally connect the "Admin UART" USB port on the Anaren Board to your computer.

Get the Software

The Anaren module uses a framework from Emmoco to make development easier. Go to www.em-hub.com. You will need to create an account to be able to download the software. Click on the "Sign-Up" link. The access code that it wants is printed on a sticker on the Anaren board. It is on the black connector, on the far left of the above image. Enter that code and your other information. Next, click on the "Downloads" link and select v13, and then "Em-Builder IDE Container" for your operating system.

Install the Software

After it downloads, install the software. It will also install some drivers too. When you first start the application it will look rather empty, like below.
Now we'll follow along the instructions in the left pane. Select Help > Em-Builder > Em-Hub Credentials and enter the same username and password you used to create your account.

Update Development Board Firmware

Next, you'll connect to the board and update the firmware. The hardest part on this step is figuring out which serial port is used for the "UART-ADMIN" port. I tried each of them but figured out finally that it was COM75 on mine. If the application is showing an error, then one thing to try is to press the "MCU Reset SOC" button on the Anaren Board; that allowed the two devices to connect. This took me awhile to figure out.

Once you have updated the firmware, it will be happy:
If you want, you can also change the name of the device here. Call it "SHAMU" or whatever you would like as long as it's all upper case. Now we're done updating the module firmware. If you want, you can remove the USB cable from the "ADMIN UART" port now.

Get the Example Projects

Now we need to import some examples so that we can start playing with them. GO to File > Import > Em-Builder > Example Projects, and select EK-TM4C123GXL as shown below.



Now, the application should have a few example projects:

Running an Example Application

Now we are ready to do something useful. Be sure that the USB cable to the Tiva board is connected to your computer. Right-click on the "Blinker-EK-TM4C123GXL" example and select "Run Em-Builder Example". It should compile and build, as shown below.
Now, open the "Em-Blinker" application on your phone. It should look something like below:
It doesn't do anything yet, you need to tell it to connect to your board. Press the button in the upper right corner to select the board. Previously we named our board "SHAMU" so we select that one. On the Tiva board, the Red LED should illuminate to indicate that it is connected to a phone.
Now, press "Start" or "On" depending on your version of Em-Blinker, and you should now see the blue LED on the Tiva board blink.

Conclusion

In this tutorial we started from zero and ended with a phone connected to our embedded device. Next time we'll look at making our own application.

Wednesday, April 30, 2014

The Easy Way to Use Thermistors


 A Thermistor is a resistor that varies its values with temperature. These are fairly inexpensive and easy to use with a microcontroller that has an analog to digital converter (ADC). However, figuring out how to compute the exact temperature is not simple. Here I go through the process that I use. I call it "The Easy Way" because I use Excel and Word to do the heavy lifting for me.

There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). Simply, for NTC thermistors, the resistance goes DOWN when the temperature goes UP.

In a circuit, thermistors are typically half of a voltage divider. In our example, the top half of the voltage divider is a 10k Ohm, 1% fixed resistor, powered by Vref, 2.5V. The bottom half of the voltage divider is a 10k Ohm 3% NTC thermistor.

Step 1: Get the temperature to resistance table

The math to convert from temperature to resistance is quite complex, so the manufacturers publish a table containing the resistance to temperature values. For example, see this link.

Step 2: Get them into Excel

For the table in the previous link the values were in a PDF sheet. But we need them in Excel. If you have a lot of time on your hands then you can manually retype them all. But I'm lazy, so I did some creative cut/paste from the PDF file into a Notepad file. You need a table consisting of many rows, each row containing the Temperature (in C) and also the Resistance (in Ohms). In Excel I also added another column containing the temperature in degrees Fahrenheit. This is just for convenience and not used in any calculations. The equation to convert from C to F in excel is the following, assuming the value in C is in cell A24:
=CONVERT(A24, "C", "F")
Below we see what our table looks like after we have all our values. We only computed for the temperature range of 0C to 85C because that was our product requirements.

Step 3: Get your ADC working

Use an oscilloscope to measure the output voltage of the voltage divider, while also measuring with the ADC. This will help you calibrate your calculations. Be sure that you know the resolution of your ADC to get the corresponding maximum counts. For example, a 12 bit ADC has 4096 possible values. Obviously to be able to measure temperature you will need to measure the voltage output from the thermistor voltage divider.

Step 4: Math Time

First, a few definitions:
  • R1 = our "top" resistor, in our case the fixed 10kOhm value
  • Rt = the varying thermistor resistance
  • Vref = the voltage supply to the top of the voltage divider, in our case 2.5V
  • Vt = the output voltage of this voltage divider
The equation for the thermistor being on the BOTTOM leg of the voltage divider is:
Rt                    Vt
-------       =   ---------
(R1 +Rt)          Vref

This can be rearranged to:
Rt = R1 / ( ( Vref/Vt) - 1)
Now, test this with your circuit, and verify that you are seeing reasonable values.
Next, we need to convert this to ADC measurements. But first a couple more definitions:
  • ADC= the value we measure from the ADC, in "counts"
  • Resolution = The resolution of the ADC. In our case, this is 4096 because we have a 12-bit ADC.
The ADC equation is:
Vt = (ADC / Resolution) * Vref
We can substitute this into the previous equation to get our magic equation:
ADC = (4096) / ( (R1/Rt) + 1)

Step 5: Make a Table

We can use this equation to create a table in Excel that contains the following tuplets:
  • Temperature (in C)
  • Resistance (in Ohms)
  • ADC Measurement
Using the Microsoft Excel Equation shown below. This assumes that our other resistor is 10k ohms, and our ADC has 4096 counts of resolution. In this case the raw resistance value is in cell C22. We
=((4096)/((10000/C22)+1))
However, this will give us decimal values. We want to round this off, so we use the following:
=ROUND(((4096)/((10000/C22)+1)), 0)

Below shows the table with the ADC values added:

Step 5: Convert this into a C Array

Here's where our laziness comes into play. We're going to use Microsoft Word to convert the list of values into a C array. First, select only the ADC column, starting at the value for 0C. Paste this as plain text into a new Microsoft Word file. Be sure to paste as plain text.
Now we have a wonderful list of values. Next, use Word's find/replace option to replace the carriage return with a comma and a space, as shown below. Note the find string is "^p" without the quotes, and the replace string is ", " without the quotes.
This performs a bit of magic, turning our long list into something that's starting to look like an array:
Now, just wrap this with the variable definition and a comment explaining what it is, and we're done.

/** ADC values which correspond to temperature starting at 0C up to 85C*/
const uint16_t adcToTemperature[] = {3008, 2973, 2937, 2900, 2864, 2827, 2789, 2752,
2714, 2675, 2636, 2598, 2558, 2519, 2480, 2440, 2401, 2362, 2322, 2283, 2243, 2204,
2165, 2126, 2086, 2048, 2010, 1971, 1934, 1896, 1859, 1822, 1785, 1749, 1713, 1678,
1643, 1609, 1575, 1541, 1508, 1476, 1443, 1412, 1381, 1350, 1320, 1290, 1261, 1232,
1204, 1177, 1150, 1123, 1097, 1072, 1047, 1022, 998, 975, 952, 929, 907, 886, 865,
844, 824, 804, 785, 766, 748, 730, 712, 695, 679, 662, 646, 631, 616, 601, 587,
573, 559, 545, 532, 519};

Step 6: Get the Temperature for ADC Reading

We now have our array of values. To get the temperature we just iterate through this table, looking for the closest match. We're not doing any interpolation or anything fancy, just a straight look-up. If you need more accuracy then do something different.
/** Gets the temperature in degrees C for the specified ADC reading.
 * @param adc the reading from the ADC
 * @return the temperature in C, or ERROR_TEMPERATURE_NOT_FOUND if the value was not found.
 */
uint8_t getTemperatureForAdc(uint16_t adc)
{
#define ADC_RESOLUTION                        (4096)

    if (adc > ADC_RESOLUTION)
    {
        return ERROR_TEMPERATURE_NOT_FOUND;  //error - this should not happen
    }

    /* Now iterate through the table, looking for the nearest match */
    uint32_t iterator = 0;
    while (adc < adcToTemperature[iterator])
    {
        iterator++;
    }
    if (iterator == ADC_TO_TEMP_LOOKUP_TABLE_ROWS)
    {
        // We've gone through the entire table but haven't found a match
        return ERROR_TEMPERATURE_NOT_FOUND;  //error - this should not happen
    }
    return (iterator);   // The temperature, in degrees C
}

Conclusion

We now have an easy and fast way to get the temperature from the thermistor.