Pages: [1]   Go Down
Author Topic: TCS230 clarified. Hardware Modified, code strengthened, hair lost.  (Read 816 times)
0 Members and 1 Guest are viewing this topic.
London, UK
Offline Offline
Newbie
*
Karma: 4
Posts: 49
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I recently acquired a batch of TCS230 colour (I'm British!) sensors from ebay very cheaply. I then hunted around for some sample code and libraries so I didn't have to re-invent the wheel. I spent a whole weekend wrestling so I thought I'd share my pain so that you don't have to go through it too.
My Sensor "cheats" in that it ties !OE to GND, meaning that the sensor is active all the time. This is bad for two reasons:

1) You can only ever have one wired to the same circuit, since they will all be chattering at once, and you can't tell who's who. The whole point of the "output enable" is so that you can ...er...enable the output - usually when your code is ready to start reading a sample.

2) The only sensible way to use the TCS230 is to hook OUT to the H/W interrupt pin and count the number of Square Waves in a given period, since the device outputs a 50% duty cycle SqWv whose frequency is proportional to the luminance of the selected color of LED. The TCS-230 can pulse at a rate of up to 24kHz according to the spec sheet. That's 24,000 times a second. Every time it does that, it calls your Interrupt service routine to count a pulse. So??? That's what we want, isn't it? Yes, but ONLY when we are counting pulses! Our program does other stuff too and having it it buzz away pointlessly in the background causes 3 problems:
  a) It wastes CPU power. This may sound pernickety cos even a UNO has some to spare if all its doing is reading this sensor..."If"
  b) Its potentially harmful. IT does nothing - apart from corrupt internal variables, produce "noisy" spikes and incorrect values in your results and even cause other very-hard-to-pin-down timing bugs. (More on this later)
  c) Its not good STYLE! That may sound trivial, but cleaner code is easier to debug and getting into good habits (especially in interrupt-driven realtime code) will pay huge dividends as you get more advanced. Timing and synchronisation bugs are notoriously hard to find yet very easy to create! It's not like the UNO has a full featured GUI debugger to help us, is it?

A good example is the lost weekend I just spent before I realised that !OE was tied to GND on my board, and wondering over an over why driving it HIGH didn't stop OUT pulsing! Here's the fix: (apologies for the poor quality..phonecam!)



So you select RED, count pulses for X amount of time, select GREEN, count some more, SELECT BLUE etc then do some scaling with the values to give an RGB triplet whose range must always be 0-255,0-255,0-255. Starting with the output disabled*, the process is to set up the chosen color, then enable the chip and count the pulses. And repeat the process forever.

To do the counting we have to measure time, so we need a timer counter or "tick", i.e. we have to start a clock, count - say - 1 second then see how many pulses came in during that period. In an Ideal world, we would start the clock at the same time as we enable the chip output and stop it as soon as it expires. (We don't need to count pointless timer ticks, either). For reason I shall explain, it's not easy to do that.

Luckily some nice people have written a library called "TimerOne" which uses the low-level registers of the 328p chip in the arduino to provide some easy-to-use timing functions. The problem is that there are several versions around, and some of them don't work! Make sure you have the absolute latest. The one I finally found has this line in it:

Code:
*  Modified April 2012 by Paul Stoffregen

So don't use anything you find before that date. (Thanks, Paul!)

Code problems

The biggest sin was not using the "volatile" qualifier on variables used in Interrupt Subroutines (ISRs). This topic can get complex - the simplest rule is ALWAYS DEFINE VARIABLES USED IN ISRs AS VOLATILE. Get into the habit. Some times you don't need it, but until you get to the point where I don't have to explain why, obey the above!

The reason is this. The C Compiler can't tell that different parts of your code run "at the same time". It sometimes uses tricks ("optimises") to make code run faster which will break badly if something else (i.e. your ISR happens to try to access the same variable at the same time. Imagine this:

Code:
void freqCount() // ticks once per external INT (on pin 2)
{
  g_count++ ;
}

That's the ISR in my code that counts the pulses from the TCS230. You might think it has only one instruction, which in C or C++, it does, but that breaks down into many low-level instructions. For a start, Arduino is an 8-bit architecture and if g_count is a long, it will need more than one byte to hold it. So, when adding 1 to it, the compiler has to:

1. Fetch the low byte.
2. Add 1 to it.
3. Check for arithmetic carry]
4. If needed, fetch the high byte and increment it too
5. save the low byte
6. save the high byte

(PS those aren't the EXACT steps...)

Now then. Imagine a pulse just came in so we need to count it. Also imagine some other part of our program is buzzing in a loop displaying the current value of g_count, e.g. fetching it every second. Let's now assume that the "fetch" of this second loop occurs between steps 5 and 6 above. We will get the new low byte but the OLD high byte, as it hasn't been saved yet!

In short, what we get is anything from a minor unnoticeable glitch to planes crashing, life support systems failing, and power grids shorting out - depending on the application. Granted our color sensor isnt going to kill anyone if it goes wrong, but I can assure you it WILL go wrong (as will any other such code anywhere else) unless you protect your variables. Arduino has an easy way to do it:

noInterrupts(); // will immediately halt all interrupts and therefore g_count is guaranteed correct / stable
// use g_count somehow
interrupts(); // Carry On Ticking

If you are quick, you won't even miss a pulse. On the other hand, if they are REALLY fast, you might. It's up to you to weigh missed ticks vs obscure bugs / corrupt values. By the time you need to make that choice, you will know more!

The next problem I found was slack use of "int" for everything. In some operating conditions, the TCS230 will generate far more pulses than can fit in an int and thus they will silently "wrap round", giving completely bogus results!

We have 32k to play with, we aren't life-threateningly time-critical, using long as the basic type isn't going to hurt anyone and will actually make the code work better.

Pins S0 / S1 act as a frequency pre-scaler. This is fancy talk for "slow the pulse down". If you set them both to LOW it sends the chip to sleep and so you can get the same effect as the OE pin (albeit with twice as much code) without having to take a scalpel to your baseboard.

At low values (2% S0=LOW, S1=HIGH) AND with a short SAMPLE time...your pulses will fit an int. Crank it up to 100% and use a 2 second SAMPLE and the values you get back will confuse you! Greens will appear red, trains will crash etc because you will be getting over tens of thousands of pulses within your time period. I wanted to experiment, to find optimum values of sample vs freq scaling to get the best trade off between accuracy / and responsivity. Don't forget we need to do the reading three times R,G and B,  so at 2 sex SAMPLE it will take 6 secs between valid readings!

Ideally we want the shortest possible sample time. To maintain accuracy, we must increase the pulse rate. Somewhere in between is the best combination for your app - and only you know that.

On to the code then. It's a test bed as described above. It doesn't do white balance calibration - and it doesn't scale the raw values to 255 because I'm still playing with it and deciding whats best for my app, but it does what it says on the tin and you can build on it:

(SEE other post for code - this was too long!)
http://arduino.cc/forum/index.php/topic,134059.0.html

IMPORTANT NOTES
We can't actually stop TimerOne from within get_freq because of a problem known as "re-entrancy" (and possibly interrupt nesting too). What this means is that if you interrupt an interrupt...sometimes bad things can happen, so lots of code either a) ignores it and hopes it won't ever happen...or b) disallows inetrrupting an interrupt. Effectively, in freq_count() ISR, Arduino is surrounding the code with noInterrupts()/interrupts. The same may well be true in getFreq(), but I lost the will to live at that point. What I can tell you is that any timer call in there will just hang the app!

Also note that Arduino uses interrupts for delay() and serial comms, so keep em off as briefly as possible. For example in the main loop they are as close as possible to the actual point of use, rather than at the outer edges of the loop.

IF you needed to use them, that's where they'd need to be, even though they are currently commented out in my code.

I confidently predict that if you are using interrupts and you don't at least THINK about noInterrupts() / interrupts() at least once, YOUR CODE WILL BREAK ONE DAY!

I hope you found this interesting or useful!

Phil
« Last Edit: November 24, 2012, 09:11:47 pm by BareMetal » Logged

See more 'duino / lego / robotics / electronic stuff on my blog http://philipbowles.com/blog

Sydney, Australia
Offline Offline
Edison Member
*
Karma: 27
Posts: 1178
Big things come in large packages
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for this post, certainly saved me the pain of the GND/OE connection.
« Last Edit: February 08, 2013, 10:15:33 pm by marco_c » Logged

Arduino libraries http://arduinocode.codeplex.com
Parola hardware & library http://parola.codeplex.com

London, UK
Offline Offline
Newbie
*
Karma: 4
Posts: 49
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Glad to have helped a fellow hacker. I have learnt so much from this forum and other arduino contributors that its only fair to give what little back that I can.

Phil
Logged

See more 'duino / lego / robotics / electronic stuff on my blog http://philipbowles.com/blog

Pages: [1]   Go Up
Jump to: