Google Analytics

Showing posts with label Electronics. Show all posts
Showing posts with label Electronics. Show all posts

Tuesday, June 26, 2012

Banjo on - AVR Bootloader with USBtinyISP

I've been really struggling with something that just didn't make sense. Part of the reason is incomplete information on my part.  But part of that blame goes to those that are selling ISP programmers in not making this clear.

I'm going to put this here in the hopes that it helps someone, and also so I can refer back to it in the future!  However, this post is going to be subject to change as I learn more or need to correct it.


I built this Minimilast EvilMadScientist board for ISP programming, an ISP (In System Programmer).  Only it didn't work for me.  There wasn't a problem with the board; there was a problem with my understanding.

I had this device (USBtinyISP) to use to with the board above.

I never could get things to work...until I found out ONE THING...

When you buy an ATMega328P without a bootloader, by default it comes with the fuse set for using the internal crystal.  However, if you place a bootloader on this chip via an ISP such as the USBtinyISP above,   then it changes the fuse to use an external crystal....at least that's my understanding right now, and it appears to be right based on ability to now place a bootloader on a chip.


Burning a Bootloader
So, here's my understanding of how to place a bootloader onto a chip that doesn't have a bootloader.  It will use the AVRDUDE to load the data, and it will need to have an device driver installed for Windows (I don't know about Linux of Mac).  See this for details and a link, and scroll down to the Description section for info on this.

Now:

  1. Place the chip onto the device that will hold the chip, such as the Minimalist EMS board above.
  2. If it's a new chip and doesn't have a bootloader on it, then you can use the USBtinyISP to program it by connecting the USBtinyISP to the header on the board.
  3. Using the Arduino 1.0 IDE, select: Tools->Board->Arduino Duemilanove.  This is what I have to do on mine to get it to work....not sure why this matters; I could be wrong, since we aren't communicating to a board in this case.
  4. Again, on the Arduino 1.0 IDE, select: Tools->Programmer->USBtinyISP.
  5. Again, on the Arduino 1.0 IDE, select: Tools->Burn Bootloader.  If this doesn't work for you, then I can't help you yet as I don't know enough yet!  So have a look at this site and also anything else you can dig up via google.
If you've got a chip that you want to add code to it, and it has had a bootloader on it before, and for some reason it won't work anymore (e.g., gets a synch problem from the Arduino 1.0 IDE), then you may need to reload a bootloader.  However, if you need to do so, the fuse may have been set to use an external crystal.  So now, you are going to have to add an external crystal to the board, and then you will be able to push a bootloader onto it, and then use it in an Arduino board.  

NEEDS SOME MORE WORK

To transfer a file to a chip via the ISP and the USBtinyISP, you'll need to open a command prompt and use avrdude.  Note that this approach, not using the Arduino IDE, and directly using avrdude, means you are going to be transferring a HEX file.

How do you get a .hex file?  Well, it's actually produced each time by the Arduino 1.0 IDE.  You don't normally see it because it's placed into (at least on my computer!  Your's may differ!) C:\Users\\AppData\Local\Temp\.  (Note: you can probably change the Arduino 1.0 IDE preferences.txt to put it somewhere easier to get to).  (Note: AppData is a hidden directory, so you'll have to enable it in your file folders setting to even be able to find it! - one way to locate it is to do a file search for the file.)

On my sysem, doing the compile under Arduino 1.0 IDE for my Blinky2.ino produces a hex file named Blink2.cpp.hex, and places this into a new file folder (see the date/time stamp on the folder) in the AppData folder.  This is the file I'm going to transfer!

And here's how I'm going to transfer it to the chip.  If the chip has a bootloader on it, this is going to overlay it, so the bootloader won't work now on a Arduino.  So you just got 2K back from not having a bootloader.  However, if this chip had a bootloader on it at some point, then the fuse is set for external crystal, and won't run unless you either change the fuse setting (I'm not getting into that here - search google), or have a crystal on the target board.  It's also going to have to be there for the transfer to work!

Here's the command line (in a command window) that's going to do the transfer (make sure you've installed WinAVR per the link above with the driver info - if you get a 'command not found' error, then either your PATH isn't set up in your environment variables, or you haven't installed the drivers and WinAVR).  
        
         avrdude -p m328p -c usbtiny -U flash:w:Blink2.cpp.hex

Sunday, April 1, 2012

Arduino - Obtaining User Values Using Potentiometer


Obtaining User Input to an Arduino Using a Potentiometer

(code example at end)

There are many times where it's convenient to enter in values using a simple potentiometer instead of a GUI interface. An example would be whereby a user wants to set the desired temperature on a thermostat.

A potentiometer has three (3) leads coming from it. Typically, the center lead is the 'wiper'. The wiper has the ability to vary the resistance from one end of the potentiometer to the other end.

A potentiometer has a maximum resistance between two leads, typically the first and last lead, and an ability to vary the resistance seen on the other lead, the wiper.

Another way to think of this is to realize the potentiometer's wiper has the ability to provide 0% through 100% of the total resistance available through the other two (2) leads.

Per Ohm's law, which states the interaction between three (3) entities – resistance, voltage, and current – if a resistance is connected to a voltage source, than there will be a voltage drop across the two ends of the resistor.

A potentiometer is a resister that also includes a third lead – the wiper, can can move between the two ends of the resistor, and is capable of varying it's resistance while doing so. Because of this, the wiper lead sees a different voltage.

The voltage at the wiper can not be more than the maximum voltage connected on the voltage-in side of the resistor, when measured to the other end of the resistor. Likewise, the voltage at the wiper can not be less than the minimum voltage connected on the voltage-out side of the resistor.

So the voltage seen at the wiper will vary between 100% of the voltage at one end of the resistor, and 0% of the voltage at the other end of the resistor.

In a linear potentiometer (as opposed to a non-linear potentiometer, such as an 'audio' potentiometer, which is logarithmic), turning the potentiometer 5% clockwise will result in a change in the resistance seen at the wiper, by 5%. If there is a voltage applied to the first lead of the potentiometer, and ground and the last lead of the potentiometer, then this 5% change in the resistance will result in a 5% change in the voltage.

The voltage will drop across the potentiometer's wiper. Remember, it will either be at 0% of maximum, or 100% of maximum, or anywhere in between, but can not exceed those two values.

When working with an Arduino, I'm not typically concerned about the actual voltage present at the wiper. Instead, I'm concerned about the position of the wiper. For instance, when using a potentiometer to turn a setpoint up or down for a thermostat, I'm not interested, as a user of the thermostat, of the voltage that is being changed; instead, I'm interested in the temperature represented by the potentiometer, and how I'm changing it.

For this reason, when using a potentiometer in an Arduino circuit, I'm typically using it as a ratio-device. I'm more interested in the ratio, or percentage, of the position of the potentiometer's wiper, not the voltage being read at the wiper.

Here's what I mean by that.

On my porch heater thermostat, I have placed a potentiometer, which I use to set the temperature that I desire the porch to stay at. By setting the potentiometer's value, I am setting the temperature I desire for my porch.

If the potentiometer is connected at one end to a 5 volt power supply, and the other end is connected to ground, then the wiper, when turned, is capable of being at any voltage between 5 volts and 0 volts, inclusive.

But I don't set my porch heater to a voltage, I set it to a temperature.

On my porch heater, I decided I wanted to be able to specify (the setpoint), a temperature between 70 dF and 75 dF. That's a 5 degree F range. Thinking in percentages, 0% would be 70 dF, and 100% would be 75 dF. 50% would be half way the range of 5 degrees, or 2.5 dF. Since my bottom temperature is 0% at 70 dF, and 50% is half way between a range of 5 dF, thus the 2.5 dF, then that 2.5 added to the 0% of 70 dF is 72.5 dF. So, if I set my potentiometer to 50%, I'm actually setting my temperature to 72.5 dF.

Well, OK, you might be thinking, but how do I get voltages and percentages into temperature?

That's where software comes into play – the flexibility of software.

I'm constrained by the hardware here – the only thing I've got to work with here is the voltage, and it is going to range between 0% and 100% of the total 5 volts available. The Arduino is capable of reading the voltage. So it's capable of reading the 0 volts through 5 volts that are being supplied by the potentiometer. But that's all it's directly capable of doing using its Analog to Digital Converter (ADC).

So anything I want to do with the Arduino, in reading an external voltage, is going to range between this 0 volts and 5 volts. And the Arduino, in converting this analog value to digital, has the capability of dividing that 0 – 5 volts into 1024 equal divisions, because it has a 10 bit ADC converter. (Take a calculator and see what 2^10 gives you). The calculator will show you that 2^10 is 1024 individual values. But, since we are starting at 0 instead of at one, then the range is 0 – 1023.

So the Arduino converts the analog 0 – 5 volts to a digital value of 0 – 1023.

Here's where the ratios make everything simple, so it's important to understand this.

The ratio, or percentage, of the 0 – 5 volts is the same ratio, or percentage of 0 – 1023.

For example:
  • a voltage of 5 volts at the wiper, is 100% of the 0 – 5 volts. Likewise, it is 100% of the 1023 value, or 1023.
  • a voltage of 2.5 volts at the wiper, is 50% of the 0 – 5 volts, or 2.5 volts. Likewise, it is 50% of the 1023 value, or 512 (integer, not floating).
  • A voltage of 0 volts at the wiper, is 0% of the 0 – 5 volts, or 0 volts. Likewise, it is 0% of the 1023 value, or 0.

What that means, is if you can convert the input value to a ratio, or percentage, then you can apply that percentage to anything.

For instance, on my porch heater, I read the potentiometer, and get back a count of 256. To convert this to a ratio, then I'm trying to find out what percentage is 256 of the total 1024? So I'm going to divide 256 by 1024, and that will give me 25%. From a voltage standpoint, 25% of 5 volts is 1.25 volts, but I don't care about this at all. I'm only concerned with the percentage, which we've determined is 25%. But I am concerned with temperature, and my temperature range, 0% - 100% is 70 dF – 75 dF, which is a range of 5 degrees F. So 25% of the range of 5 degrees F is 1.25 degrees F, and since my base is 70 dF, and I'm 1.25 dF above that 0% value, then I'm at 71.25 dF.

So you can apply this to anything, and it's what makes a potentiometer so powerful in any circuit you are building.

The hardware gives you the range of 0 – 5 vdc, 0% - 100%, the ADC gives you 0% - 100% of 0 – 1023, and then using software, you choose what you want that to represent. In my case, I wanted that 0% - 100% to represent 70 dF to 75 dF for a porch heater. But for my Sous Vide cooker, I want that to represent 150 dF – 200 dF, which has a range of 50 dF, and using the previous example of 25% of the potentiometer, and 25% of the max 1024 counts, then I'm now talking about 25% of the range of 50 dF, or 12.5 degrees F above the base of 150 dF, or 162.5 dF.

Using software, you decide what the final values are that you are representing with the potentiometer. Then taking the percentage of the potentiometer, and the percentage of the maximum counts of 1024, you can directly apply that to whatever you are doing – RPMs, population, weights, valve opening, whatever.

Here's a coding example.

Assume we are wanting to set a desired temperature onto a thermostat. Further assume the thermostate will be capable of setting the temperature anywhere from 70 to 75 dF. Assume we will use analog pin A0 for the wiper on the potentiometer.

void setup() {
     pinMode(A0, INPUT);
     Serial.begin(9600);
}

void loop() {
     float setpoint = 0.0;
     float volts = 0.0;
     int counts = 0;
     float ratio = 0.0;
     float baseTemperature = 70.0;
     float tempratureRange = 5.0; //75 dF – 70dF = 5 degrees F range

     //get the potentiometer's wiper value in counts, 0 – 1024
     counts = analogRead(A0);

     //get the ratio of the counts as a percentage of mximum
     ratio = (float)counts / 1023.0;
     
     //using the ratio to calc the voltage (maximum of 5 vdc). It's not used, but just for fun.
     volts = 5.0 * ratio;  //we have a max of 5 volts

     //now get the setpoint temperature
     setpoint = (ratio * temperatureRange) + baseTemperature;

     //that's all there is to it!

     // now print this out
     Serial.print(“counts: “); Serial.print(counts);
     Serial.print(“, ratio: “); Serial.print(ratio);
     Serial.print(“, volts: “); Serial.print(volts);
     Serial.print(“, setpoint: “); Serial.println(setpoint);

}


Tuesday, March 20, 2012

Using Arduino to read embedded AVR

If you are needing to communicate with an embedded project, then this may be the solution you need!

Building a stand-alone device such as a Multi-channel Thermocouple Reader with Datalogger is an ambitious project for a hobbyest.  Since it is my own design, it means, on my first cut, there isn't a PCB - everything is hand soldered - thus multiple opportunities for problems.

Being a stand-alone, embedded application (not built around Aruduino board, but instead built using the ATMEGA328P chip that is used on an Arduino) means Arduino's useful USB connection isn't there.

Program updates are pushed onto the ATMEGA chip via a ISP header I placed onto the board, using a programmer (in this case, I'm using the USBtinyISP programmer from Adafruit).   The ISP interface, which makes use of the MISO/MOSI interface, does not provide a way to communicate directly with the Arduino IDE Serial Monitor directly, so there's no easy way for me to observe data from the embedded ATMEGA, like there is on the Arduino (via Serial.print() statements).

I programmed some 'blink()' status LEDs, and that worked, but it is slow.  At some point during the build, I had enough working that I could output data directly to the SD Card, but to read that data entailed shutting down the power, pulling the card, inserting it into the PC, opening the file and reading it.  Reverse to put the card back into my project - slow....

I began to wonder if there wasn't some way I could use a separate Arduino UNO to serve as a communication hub between my embedded project and the PC running the Serial Monitor.

Looking around, I spotted this, which is using an Arduino to push a program onto an embedded chip.

There are two key things to take away from the schematic for the UNO:

  • The USB on the Arduino has an additional ATMEGA chip that enables communications, and sits between the USB and the ATMEGA328P that we program our projects onto.  
  • The TX and RX connections are used between these two chips for communications.
So, since these two chips are communicating, which is what I was wanting to do, I thought I should be able to emulate this connection.  I was getting all complicated with this before I spotted the statement: "To do, you remove the microcontroller from the Arduino board ", which meant I could pull out the Arduino's ATMEGA328P chip, then connect to my project, using the Arduino's RX and TX header (D0 & D1 on my Arduino).

I had been careful to keep the RX & TX pins unused on my project board, so it was a simple matter of soldering in two wires that I could then plug into the Arduino's RX & TX header.

Then I added some Serial.print() statements into my embedded project, and cranked it up and .... nothing appeared on my IDE's Serial Monitor.

So I reversed the wires, because this is a common issue with RX & TX - RX on one board has to connect to TX on the other board, and vice-verse, and you never know how it's been defined between the two boards, so always try swapping if it doesn't work.  

BANJO!  

It worked!  I was seeing my Serial.print() statements streaming by from my embedded project, using the Arduino as the communications hub!

Monday, March 12, 2012

Arduino - COM USB Unknown Device Problem

Every once in a while, while working on my Arduino stuff, I get this really aggravating problem, whereby my USB suddenly disappears.  This means I can't upload code to the Arduino, and I can't access the Serial Monitor to see what the Arduino might be printing out.  Windows, after working with it for weeks, if not months, suddenly decides it doesn't know what this device is!  It also decides that it will load a device driver for you.  And of course, once it's loaded this bad device driver, you can no longer talk to your Arduino.

To add insult to injury, you may have gone to the Control Panel -> Device Manager -> USB devices, and selected the 'Unknown Device', as Windows has labeled it, and attempt to update the device driver.  And that's when things really get pissie - Windows now tells me that it's using the right driver, so it's not going to do anything.

There are a lot of entries out in the web-o-sphere, where people are looking for answers to this.  Some of those answers will work, for some of the people.  Other peoples' problems might be too severe for those treatments, and so they are still looking for a solution - and I hope the solution I'm going to give you below is the one you need to make it work again!

First, try the other solutions you might find.  Only use these if those solutions haven't worked for you!!!


First, go to the Control Panel -> Device Manager, and delete every entry you see within the higher level USB entry.  In other words, in the Device Manager, enter the USB devices area, then in that area, delete everything.  Then shutdown (hard shutdown), to make sure all of the power is off of the motherboard and parts.  Now boot up. The system will start loading and defining the USB ports.  This might be all you need - it was for me a couple of times.  Try out your Arduino IDE software now.


If that didn't work, then go back and delete everything again.  Then, using a Registry Cleanup tool, like the one that comes with Symantic's Norton 360, run the Registry Cleanup.  This will remove any hanging port definitions that Windows changed when it decided it was going to help you earlier.  Shut down, boot back up, and see if things work.  Hopefully, you can stop here.

If you still aren't working, then make a backup of your sketch folders - you should have placed them somewhere else other than in the folders that hold the Arduino binaries anyway.  Then delete the Arduino binaries - the whole directory structure, from the top of the Arduino folders to the bottom, but remember - your sketch stuff and 3rd party libraries should be in another directory structure if you set things up properly.  Now download a new instance of Arduino, and install it (unless, of course, you still have the zip from your previous download of Arduino, in which case you can just install it again).  You are doing this because Arduino isn't currently 'installed' into the Windows program environment, so you can't 'uninstall' it either - you've got to delete stuff.  Now do the stuff you did earlier - USB deletions, Registry Cleanup, etc.  Now install the complete Arduino package.  See if your stuff works.  I hope it does!

If it doesn't then open the Arduino IDE, go to FILES, then open the 'prefereneces' - it will give you the file location, in the AppData directory (it may be hidden, in which case you'll have to override Windows and make it visable).  Now rename the preferences.txt file to something like presferences.txt.old.  Now restart the IDE; this will recreate the preferences.txt file.  Now try to see if you can access the port.  If not, then do everything from beginning to end, including the deletion and recreation of the preferences.txt file, and see if it works...I hope it does, because this has been the answer to all my problems that I've encountered!

Banjo

Saturday, March 10, 2012

Banjo - Regarding the Arduino


I'm pretty serious about BBQ. It represents one of those things the South is known for, and it can also taste great, if you are lucky enough to find a restaurant, or friend, that is skilled at making it.
When I was first starting out making BBQ, I didn't have a clue where to start (see this previous articleon BBQ for more insight), which certainly made for interesting dinners! However, with a patient wife and smart children, we all benefited – although the suffering endured may have been a little intense.
At some point, I realized the main ingredient was missing. No, I'm not talking pork here. I'm talking about information. That was the key ingredient that was missing. With no mentors nearby, and with it being pre-Internet, information was hard to come by. So that meant one thing: tests.
Cooking BBQ means you are dealing with several things, all of which will affect the outcome:
  1. Temperature of the cooking chamber (oven) – influences time
  2. Time at temperature – influences temperature
  3. Size of the meat (weight, dimensions) – influences time
  4. Source of heat (wood, gas, electric) – influences flavor
The two that I was struggling with the most were: time and temperature.
So, if I was going to run tests to collect information, then I was going to focus on time and temperature. The time was pretty easy to keep track of, but the temperature was a little more difficult. In your typical home-style offset smoker, it has two chambers – the combustion-chamber (the firebox), and the smoke-chamber (the oven). There were several temperatures of interest: there was the temperature of the combustion-chamber, the temperature of the smoke-chamber (and this could be different in different areas), the temperature of the meat, and outside air temperature. In other words, a lot of temperatures!
At first I tried just plugging a big meat thermometer into the meat and leaving it in, where I would occasionally reach in and take a temperature reading. But this was problematic – it let heat out of the smoke-chamber, the face of the dial became black and difficult to see as smoke residue built up on it, and I wasn't too sure that the metal probe wasn't conducting enough heat, over time, to influence the meat temperature near the probe. Because of these issues, I also tried an 'instant read' thermometer. But that didn't work too well – it still let heat out of the oven, and the placement of the probe was never consistent, so I could never be sure I was getting an accurate reading - since heat migrates into meat from the outside in to the middle, the portion of the meat closest to the outside will be the hottest, while that in the middle will be the coolest – at least until all the moisture has cooked out; a dried, hard piece of meat will have a consistent temperature throughout.
What I needed was some sort of device could take all of the different temperatures and then record each of these different temperatures for me.
I knew from my work in process control at nuclear plants that thermocouples were a good choice for obtaining the temperatures: they were accurate enough, they were robust enough to take the combustion-chamber temperatures, and they could all be collected together outside of the cooking environment, which meant I didn't have to place the recorder into the smoker.
However, thermocouples can't be used directly; they require an amplifier, and another device to measure the ambient temperature in order to derive the temperature under sample.
At first, I thought the most obvious solution was to buy something that would plug into my computer that was capable of taking the temperature samples and then recording them in my computer. Not a bad idea, but it turned out that this was way-too-expensive for a hobby!
Researching led me to microcontrollers. In particular, Atmel's AVR line of microcontrollers. A microcontroller is an integrated circuit – popularly known as a 'chip'.
A microcontroller is an interesting thing. It can be programmed, so it has the benefit of being capable of running software. Nothing much new there, except the software is all in the chip, instead of being on a hard disk, like it is on your computer, which is then transferred into your computers memory before being read into your computer's CPU. The microcontroller also has direct capabilities to interface with the physical world, in the form of sensing voltages, and can also send out voltages, on different pins on the chip. This meant it could interface with a thermocouple. It can also communicate directly with another computer, which meant it could send the data to the computer where it could be recorded.
So, I built a multi-channel temperature sensor and data logger, using one of Atmel's microcontrollers. It was a little bit of a challenge, but the results gave me data on all of the items I was interested in, at a sample rate of once-per-minute. I could have gotten more samples (e.g, one per second), but this seemed enough. I programmed a small dedicated web-server that would display time and temperature on the internet so I could pull it up at work to see if I needed to head home over lunch to make any adjustments. And, once you've got all of this, you may as well hang an inlet-air valve on the smoker and controller the smoke-chamber temperature with the microcontroller so you can do something else all night, like sleep!
A lot of time and effort went into this – but not much money, as all of the items were inexpensive. The effort was in acquiring the knowledge to put everything together. I was fortunate, in that I already had an extensive electronics background, as well as an extensive software programming background, but even then it took a while.
Which leads me, after a long segue, to the Arduino.
There is some confusion as to what an Arduino actually is, so I'm first going to explain it. It is an open-source hardware electronic board. Being open-sourced, it can be manufactured by many companies, or even yourself. It comes with various chips on it and hardware connections to make it easier to interface into the real world, for everyday hobbyist. There are different boards available, but one of the newest is the Arduino UNO. The main chip on the board is an Atmel ATMEGA328P – this is the microcontroller. It also has a voltage regulator, a couple of LEDs (light emitting diodes), and places where different devices can be plugged in. This board runs around $30.
The Arduino is also a software programming environment, known as an IDE (Integrated Development Environment), that runs on your computer. This part if free. (www.arduino.cc)
The Arduino was developed with the objective of making microcontrollers available to artists, fashion designers (clothing like the lighted cloths worn at this year's Super Bowl half-time show), and everyday hobbyist. Therefore, a lot of the things that I struggled with (above) have been simplified, with the result that whole companies (www.adafruit.com, www.sparkfun.com) are now up and running to supply items that can plug into this Arduino (known as 'shields'). One of those shields available, for about $20 from adafruit and/or sparkfun, is a thermocouple reader. This means, for about $50, any hobbyist can make a small investment in time, and have a BBQ Temperature Sensor and Data Logger!
An Arduino is a perfect solution for taking BBQ temperature data from tests run, among other things. It has program memory, for the software, and data memory (for the data), all on the chip. This means the data is stored directly in the Atmel chip. It also means it's directly available to transfer into a computer for graphing or other use. And, again, it can also be programmed to interface with an inlet air valve to control the smoke-chamber temperature, while you do something more constructive, like sleep.
Well, once you've got an Arduino, that old saying comes into play: to a boy with a hammer, the whole world is a nail! Well, to someone with an Arduino, the whole world, every device, seems like it needs one!
Here's how I'm currently using Arduino's around our house (a link to my Sous Vide Controller for crock-pot):
  • A thermostat to control the gas heater on our porch, without interfering with any of the heater's built in safety devices.
  • A precise crock-pot temperature controller. Want to use a crock-pot as a small, inexpensive, Sous Vide device? No problem!
  • Run time vs. temperature tests on meat in Sous Vide
  • Automatic watch winder. Did you know these things are $100 - $3000!
And here's what I've got under development :
  • 4 or 8 channel thermocouple datalogger to 4 GB SD card.
  • Better watch winder. This sounds so easy, but the issue is that each watch manufacturer has different criteria on how to wind their watch. Rolex requires about 600 revolutions per day, alternating clockwise and counterclockwise; Breitling requires abpit 800 revolutions per day, all clockwise. For the Breitling, that's about 1 revolution every 15 seconds, followed by a pause of about 1.5 minutes.
  • Inlet air valve for large smokers.
If you've got a teenager that needs an interesting activity, have a look at Arduinos. They are inexpensive, fun to work with, and very educational. In the Alpharetta area, both Fry's and Radio Shack carry Arduinos.
I'll be writing more about this interesting device in the future.
Here's some interesting links:

I MacGyvered it!



The new patio heater's pilot light just wouldn't stay lit.

I was doing everything that should be done; I was holding in the throttle valve while striking the lighter.  It would light; it just wouldn't stay lit.

I held it in for 30 seconds per the manual.  Didn't stay lit when I released the valve.

I held it in for 1 minute.  Didn't stay lit then either.

Held it until dooms day; still didn't stay lit.

What's a Banjo man gonna do?  His wife had SPECIFICALLY said her shoulders were hurting from working on the roses in the garden, and SHE WANTED SOME HEAT from that patio heater we had bought to pour down on her shoulders.

Damn thing just would not stay lit.

So I gave up on the BS technology safety features that were built into this device, and I MACGYVERED IT!

I BYPASSED that SOB that was causing all the problems.

YEAH!

Now, we're listening to some Blues!  And feeling some HEAT!

Righteous!  Knowledge of Electronics Rules!

Monday, February 13, 2012

Banjo's Sous Vide Controller - Arduino Thermocouple Controller

Arduino Sous Vide Controller!

This is part 3 of a 3 part project
             (Part 1 is a Timer Controller)

Note: the software limits you to a minimum food-safety temperatuer setpoint of 130 dF.  





This project makes use of a previous project, the Arduino Thermocouple Controlled Thermostat for the ability to precisely detect and control the temperature of the water-bath, along with a device - power switch tail - from Adafruit.  This power switch tail needs 5 vdc to drive it.




So I've changed the wiring from the pure NO contacts on the timer and thermocouple controller projects (see previous posts), to have a switched 5 vdc across the contacts.  So, as wired, the 5 vdc comes from the Arduino 5 vdc pin, then through the NO contacts, then out to the power switch tail, and then back to the Arduino gnd pin.  Per the power switch tail, it needs 5 vdc at 40 ma, so that's the reason I'm still using the relay I used in the thermocouple controller.



This version of the project does not incorporate a display, in order to keep the project costs and simplicity down.  However, the temperature setpoint will be able to be established using a DVM, or analog panel voltmeter.  I used a (currently listed at $5.00) 5vdc panel meter from SparkFun.   This works great, as I assigned it 0 - 5 volts to correspond to 100 - 150 dF, which the project can generate using a PWM pin (I used pin 11 in my software).  That's 10 degrees F per volt, or 1 dF per 1/10th volt, which is easy to read on the little meter.  I mounted this on my cigar box (see picture).  Note: with a simple software change, I can use this same device for the gas heater thermocouple temperature controller, and assign it a range of 0 - 5 vdc for 70 - 75 dF, then each volt equals 1 dF.






Some useful links:



Software Changes
Note the software changes from that posted for the thermocouple controller.  Not significant, but I added:

  • PWM output on pin 11, in order to be able to view the setpoint without having an expensive LCD display or a laptop connected.  You can use a DVM, or as I did, mount a 5 vdc panel meter on the face of your project box.
  • Software generate the analog voltage values.
  • Changed software to reflect use of 'const'.
  • Gave predefined values for Sous Vide vs thermostat values.
I'm going to go ahead and post this, along with the software.  I'll update a Fritzing layout later and add back.


SOFTWARE LISTING



//Author: Banjo 1/29/12
// Rev 1.0 added ability to use dvm to monitor setpoint via pwm output
//     1.1 refactored some duplications in turnHeaterOn() and turnHeaterOff()
//     1.2 change setpoint so it can't be less than 130 dF for food safety reasons.  Added LOWEST_TEMPETURE_DISPLAY vs LOWEST_TEMPERATURE_SETPOINT
//
//  HARDWARE: 
//  - This program makes use of the Thermocouple breakout board from www.adafruit.com MAX6675, inserted into 
//    Arduino UNO Digital pin positions 2 - 6.  Correctly inserted, this sheild will lay over the board, not project outside of it.
//  SOFTWARE: This program makes use of a thermocouple library from: // www.ladyada.net/learn/sensors/thermocouple.html
//    you will also need to install the library and rename it, from that same page.
//  - displays 
//       RED - heater is on
//       GREEN - heater is off
//       Alternating RED and GREEN - pilot doesn't appear to be lit.
//       5VDC volt meter.  This corresponds to temperature, with zero volts equal to your base temperature, and 5 volts equal to your base temperature plus range.
//
//For use:
//  - Wire manual heater switch to NO contacts on relay.  This relay is controlled by this Arduino.
//    These contacts are not polarized, so it doesn't matter which contact goes to which
//    leg of the switch.
//  - Wire Type K Thermocouple to screws on Thermocouple breakout board, observing correct polarity.
//      (Note: if you get the thermocouple poloratiy wrong, it won't hurt anything, it just won't work right.)
//
//for testing:
//  - If you want LED for HEATER ON, then add LED at digital pin 9, then through resistor to ground
//  - if you want LED for HEATER OFF, then add LED at digital pin 10, then through resistor to ground 
//  - add jumper from 5 vdc to pin A1 - this is the pilot light permissive
//  - add jumper from pin A0 to 3.3 vdc ref; this will supply about 66% ratio
//  - change MAX_CYCLE_TIME to something short, like 10 ONE_SECOND
//  - change IGNORE_MINIMUM to something short, like 2 ONE_SECOND
//  - wire dvm across gnd and pin 11 for PWM setpoint value in volts
//  - remember to change back!
//
//some handy time constants
const unsigned long ONE_SECOND = 1000;
const unsigned long HALF_SECOND = 500;
const unsigned long QUARTER_SECOND = 250;
const unsigned long ONE_MINUTE = ONE_SECOND * 60;
const int MAX_ADC_COUNTS = 1024; //ADC
const unsigned long SENSOR_READ_DELAY_TIME = QUARTER_SECOND;


//heater controlled by temperature
#include
//pin assignments 
const int HEATER_OUTPUT_PIN = 8;              //This controls the onboard relay that controls the heater gas valve
const int STATE_LED_PIN = 9;                  //LED if using an external state LED
const int OFF_LED_PIN = 10;                   //LED heater off 
const int REF_VOLTAGE_SETPOINT = 11;          //pwm output pin.  Connecting DVM here will give you corresponding temperature setpoint, added to LOWEST_TEMPERATURE_DISPLAY.  
const int UNO_LED_PIN = 13;                   //LED on the uno board

const int HEATER_RATIO_INPUT_PIN = A0;        //this is the onboard potetiometer that controls the ratio of on to off
const int HEATER_INPUT_PERMISSIVE_PIN = A1;   // Read this pin for input permissive from thermocouple pile.  


//     E.g., if LOWEST_TEMPERATURE_DISPLAY = 70, then 1 vdc output would be 71 deg; 2 volts = 72, ...5 volts = 75, which would be maximum value.
//for adafruit thermocouple amp
const int thermoDO = 4;  //corresponds to adafruit thermocouple amp shield
const int thermoCS = 5;  //corresponds to adafruit thermocouple amp shield
const int thermoCLK = 6; //corresponds to adafruit thermocouple amp shield
const int vccPin = 3;  //corresponds to adafruit thermocouple amp shield used via set high
const int gndPin = 2;  //corresponds to adafruit thermocouple amp sheild coupled to ground via set low
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);  //function declaration
void docommonCalcs(double *pTemperature, double *pPreviousTemperature, float *pSetpointRatio, float *pSetpointValue);

//stuff for calcs
//
//**** normal usage ****
//const float LOWEST_TEMPERATURE_SETPOINT = 70.0;  //This corresponds to 0% on temperature setpoint adjustment knob.  It is the lowest temperature setpoint.
//const float LOWEST_TEMPERATURE_DISPLAY = 70.0;   //When setpoint minimum (food safety) and display are the same, set both to be same
//const float MAX_CONTROL_RANGE_dF = 5.0;          //useful to stay with 5, as it is also the pwm output voltage capability for use with dvm for setpoint.  see showSetpoint() for insight 
//**** end of normal usage ****
//
//**** for Sous Vide cooking replace with following values ****
const float LOWEST_TEMPERATURE_SETPOINT = 130.0;
const float LOWEST_TEMPERATURE_DISPLAY = 100.0;
const float MAX_CONTROL_RANGE_dF = 50.0;
//****  these values will give you a base temperature of 100.0dF, and with a range of 50.0 dF, you will have a total control temperature range of 100.0 dF to 150.0 dF.
//****  at these values, the 5 volt meter will represent 50 dF.  So each 1/10 volt will equal 1 dF.
//****  e.g., a value of 1 volt  would equate to 10 dF, plus the base of 100.0 dF, for a total of 110 dF.
//****        a value of 2 volts would equate to 20 dF plus base of 100.0 for a total of 120.0 dF
//****        a value of 3.1 volts would equate to 31dF pluse base of 100.0 for a total of 131 dF.
//****
//****  Note for Sous vide cooking: I would not trust this device totally for setting by a volt meter.  I would 
//****       set the temperature, then verify the water bath after it has had time to stabalize by using an accurate thermometer.
//****       I would also extend considerably the amont of time needed for pasteurazation - I would not use minimum time values
//****       so I could be assured that the food has been pasteurized.  Please consult 
//****       Doublas E. Baldwin's excellant book "Sous Vide for the home cook" for information, and his web page at
//****       http://www.douglasbaldwin.com/index.html for additional safety information.
//****  Note for Souse Vide cooking equipment:  I would suggest using a crock-pot, placing it on low temperature if it is capable of 
//****       water up to 160 dF when full, or on high otherwise.  The lower setting is desirable, if usable with your cooker, in order to
//****       lesson the amount of temperature swings from heat on to heat off.  Also, use a device to control the crock pot that is capable 
//****       of safely handling the current and voltages involved.  I use something like the following device from Adafruit:
//****       https://www.adafruit.com/products/268 power switch tail, which is rated at 15 amps at 120 vac.  
//**** end of Sous vide cooking values

//const float 
float DEADBAND_dF = 0.5;
float CALIBRATION = -3.25; //Testing boiling water adjusted for altitude (-1 deg for every 500 ft).  If you don't test, then set to 0.0 
const int TEMPERATURE_AVERAGING_ARRAY_SIZE = 10 * (ONE_SECOND/SENSOR_READ_DELAY_TIME);  //Samples to be averaged.  10 SECONDS WORTH OF AVERAGES
float temperatureReadings[TEMPERATURE_AVERAGING_ARRAY_SIZE];
boolean firstTime = true;


int HEATER_ON = HIGH;  //used because may use sink current at some point
int HEATER_OFF = LOW;  //used because may use sink current at some point
int ALARM_COUNTS = 250;  //blink rate

float lowest_temperature_setpoint_ratio = 0.0; //global

void setup() {                
  Serial.begin(9600);
  // initialize the digital pin as an output.
  // Pin 13 has an LED connected on most Arduino boards:
  pinMode(UNO_LED_PIN, OUTPUT);
  pinMode(STATE_LED_PIN, OUTPUT);
  pinMode(OFF_LED_PIN, OUTPUT);
  pinMode(HEATER_OUTPUT_PIN, OUTPUT);
  pinMode(REF_VOLTAGE_SETPOINT, OUTPUT);  //Will provide PWM output voltage

  pinMode(vccPin, OUTPUT); digitalWrite(vccPin, HIGH); //voltage source
  pinMode(gndPin, OUTPUT); digitalWrite(gndPin, LOW); //current sink

  //This calculation needs to be done only once
  lowest_temperature_setpoint_ratio = (LOWEST_TEMPERATURE_SETPOINT - LOWEST_TEMPERATURE_DISPLAY) / MAX_CONTROL_RANGE_dF;
  
  // wait for MAX chip to stabilize
  delay(500);

  Serial.println("setup()...Entry & Exit"); 

}

void loop() {
  Serial.println("loop()...Entry"); 
  //digitalWrite(ONBOARD_LED_PIN, HIGH);   // set the LED on
  turnHeaterOff();
  turnHeaterOn();
  Serial.println("loop()...Exit"); 
}

float getSetpointRatio() {
  //rather than counts of 0 - 1023 (range of the onboard ADC), return the 0% - 100% of range
  float ratio = 0.0;
  int sensorValue = 0;

  sensorValue = analogRead(HEATER_RATIO_INPUT_PIN);
  ratio = float(sensorValue) / float(MAX_ADC_COUNTS);

//  Serial.print("getSetpointRatio()....sensorValue: "); Serial.println(sensorValue); 
//  Serial.print("getSetpointRatio()....ratio: "); Serial.println(ratio);
  return ratio;
}


//-------------------------------------------------------------------------------------------------------------------------------------

void doCommonCalcs(double *pTemperature, double *pPreviousTemperature, float *pSetpointRatio, float *pSetpointValue) {
  //note this uses pass-by-reference not pass-by-value in order to be able to change these values
  //warning - this method will change values on the passed references
    *pPreviousTemperature = *pTemperature; //first time will be 0.0;
    *pTemperature = thermocouple.readFarenheit();
    *pTemperature += CALIBRATION; //Determine this by testing with boiling water for your probe.  
    *pTemperature = getTemperatureAverage(*pTemperature);
    *pSetpointRatio = getSetpointRatio();
    if(*pSetpointRatio < lowest_temperature_setpoint_ratio) {
      *pSetpointRatio = lowest_temperature_setpoint_ratio;
      Serial.print("doCommonCalcs() ***!!!*** setpoint override to minimum food-safe value of: "); Serial.println(LOWEST_TEMPERATURE_SETPOINT);       
    }
    showSetpoint(*pSetpointRatio);  //pwm
    *pSetpointValue = LOWEST_TEMPERATURE_DISPLAY + (MAX_CONTROL_RANGE_dF * *pSetpointRatio);
    Serial.print("doCommonCalcs()...setpointValue: "); Serial.println(*pSetpointValue);
    Serial.print("doCommonCalcs()...Temperature avg: "); Serial.println(*pTemperature);
    Serial.print("doCommonCalcs()...previousTemperature: ");Serial.print(*pPreviousTemperature); //useful to see how noise the data is
      Serial.print(", difference: "); Serial.println(*pTemperature - *pPreviousTemperature);
  
}


void turnHeaterOn () {  //needs to be refactored with turnHeaterOff
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF
  static boolean initNeeded = true;  //needed first time we start up
  
  
  Serial.println();
  Serial.println("turnHeaterOn()...temperature controlled...entry.......................");
  
  do {  
    doCommonCalcs(&temperature, &previousTemperature, &setpointRatio, &setpointValue);
    heaterOn();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature < (setpointValue+DEADBAND_dF));
  Serial.println("turnHeaterOn()...exit");
}

void turnHeaterOff() {  //needs to be refactored with turnHeaterOn
  double temperature = 0.0;
  double previousTemperature = 0.0;
  float setpointRatio = 0.0;
  float setpointValue = 0.0;  //dF

  Serial.println();
  Serial.println("turnHeaterOff()...temperature controlled...entry......................");

  do {
    doCommonCalcs(&temperature, &previousTemperature, &setpointRatio, &setpointValue);
    heaterOff();
    delay(SENSOR_READ_DELAY_TIME);
  }  while(temperature > (setpointValue-DEADBAND_dF));
  Serial.println("turnHeaterOff()...exit");
}

void showSetpoint(float setpointRatio) {
  //write the voltage to pwm pin.
  //voltage out is defined as 0 - 5 vdc.  0 volts = LOWEST_TEMPERATURE_DISPLAY, and 5 volts = LOWEST_TEMPERATURE_DISPLAY + MAX_CONTROL_RANGE_dF
  //so, if LOWEST_TEMPERATURE_DISPLAY is defined as 70 dF, and MAX_CONTROL_RANGE_dF is defined as 5 dF, then
  // 70 dF would equal 0 Volt, 71 dF would equal 1 Volt, 72 dF would equal 2 volts, 73 would equal 3 volts, 74 would equal 4 volts, and 75 would equal 5 volts
  float voltageOut = 0.0;
  int pwmValue = 0;
  
//  setpointRatio = 1.0 - setpointRatio; //depending on which way you wire your pot, you may have to have 1.0 - setpointRatio or just setpointRatio.
  pwmValue = 255.0 * setpointRatio;
  voltageOut = 5.0 * setpointRatio;  //only valid if you keep the MAX_CONTROL_RANGE_dF to 5!
  analogWrite(REF_VOLTAGE_SETPOINT, pwmValue);
  Serial.print("showSetpoint()...pwmValue: "); Serial.println(pwmValue);
  Serial.print("showSetpoint()...voltage: "); Serial.println(voltageOut);
}

float getTemperatureAverage(float readValue) {
  static int element = 0;
  float oldReadingValue = 0.0;
  float newAverageValue = 0.0;
  static float oldAverageValue = 0.0;
  
//  Serial.println("getTemperatureAverage()...entry ");

  if(firstTime) {  //first time, there are no previous readings, so populate the whole thing with current reading
//    Serial.println("getTemperatureAverage()...First Time.");
    firstTime = false;
    for(int i=0; i<TEMPERATURE_AVERAGING_ARRAY_SIZE; i++) {
      temperatureReadings[i] = readValue;  //do the population with current reading
    }
    oldAverageValue = readValue;  //on first time, all are same, so this is also the average
    return oldAverageValue;  //bailout shortcut on first time
  }
  
  //doing it this way avoids having to loop through whole array and then taking the average
  //replace the oldest reading with the newest reading, then offset the average value
  //this is a circular queue
  oldReadingValue = temperatureReadings[element];
  temperatureReadings[element] = readValue;  //replace old value with new value
  newAverageValue = (readValue - oldReadingValue)/ float(TEMPERATURE_AVERAGING_ARRAY_SIZE);  //get averaged change between old value and new value
  oldAverageValue += newAverageValue; //change average by average increase or decrease
//  Serial.print("getTemperatureAverage()...average temperature: "); Serial.println(averageTemperature);
  
  element++;
  if(element==TEMPERATURE_AVERAGING_ARRAY_SIZE)  //this makes it a circular queue
    element = 0;
//  Serial.println("getTemperatureAverage()...exit ");

  return oldAverageValue;  
}


//------------------------------------------------------------------------------------------------------------------------------------------------------

void heaterOn() {
  Serial.println("heaterOn() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_ON);   // sink current
  digitalWrite(OFF_LED_PIN, LOW);
  digitalWrite(UNO_LED_PIN, HIGH);   // set the LED on
  digitalWrite(STATE_LED_PIN, HIGH);
  Serial.println("heaterOn() ... exit.");
}

void heaterOff() {
  Serial.println("heaterOff() ... entry.");
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);     
  digitalWrite(UNO_LED_PIN, LOW);   // set the LED on
  digitalWrite(STATE_LED_PIN, LOW);
  digitalWrite(OFF_LED_PIN, HIGH);
  Serial.println("heaterOff() ... exit.");
}  

void alarm(int sensorValueCounts) {  //needs to be refactored to use heaterOff()
  //blink both on and off LEDs
  Serial.println("alarm()....Entry...will not exit!");

  //TURN OFF!
  digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);   
  digitalWrite(OFF_LED_PIN, LOW);
  
  Serial.print("alarm()....sensor low. sensorValueCounts: "); Serial.println(sensorValueCounts);
  while(1) {
    //keep turning heater off!
    digitalWrite(HEATER_OUTPUT_PIN, HEATER_OFF);
    
    //show it by alternating blinking both LEDs
    digitalWrite(UNO_LED_PIN, HIGH);
    digitalWrite(STATE_LED_PIN, HIGH);
    digitalWrite(OFF_LED_PIN, LOW);
    delay(ALARM_COUNTS);

    digitalWrite(UNO_LED_PIN,LOW);
    digitalWrite(STATE_LED_PIN, LOW);
    digitalWrite(OFF_LED_PIN, HIGH);
    delay(ALARM_COUNTS);
  }
}