Reducing I/O requirements for optical detection

Although this project may be a bit esoteric, the concepts shown to reduce I/O requirements can also be applied to other systems.
I designed a device that has, as part of its function, multiple pipes which are referred to as silos. These silos each have a rod that can be slid in and out of the interior. The task was to detect which silos have the rod inserted in them (occupied), and which do not (empty). For the sake of this discussion, we’ll say there are 10 silos total (Figure 1).
Wow the engineering world with your unique design: Design Ideas Submission Guide

Figure 1 This design detects which of the silos are occupied by rods.
There are many ways to detect if a particular silo is occupied. For various reasons, I opted to go with an optical solution. As you can see in Figure 1, the silos were constructed each with a hole bored in its side, going all the way through. One possible per-silo configuration of an optical solution is to mount an LED on one end of the hole and a phototransistor on the other (Figure 2).

Figure 2 Each silo has a hole bored straight through its side, with a LED mounted on one end and a phototransistor on the other.
To directly connect all these LEDs and phototransistors to a microcontroller, we would need 20 I/O ports; 10 to turn on the LEDs and 10 for the photodetectors. This would be far too many I/O pins to use on a small micro. We could add some kind of port expansion circuit, but there are easier ways to reduce the needed pin count.
Here’s one option: we could simply drive all of the LED from a common port and then scan the phototransistors one-by-one to sense each silo’s occupancy status. This may take an external transistor to drive 10 LEDs, but it’s still very simple. This scenario requires only 11 total I/O ports. But that’s still a lot. Let’s look at a way to reduce the number by a lot more, down to two I/O ports.
First, for the LEDs, let’s use addressable ones. I picked the WS2812B, commonly referred to as a NeoPixel. Each NeoPixel require 5V, GND, and a single data line, and is made up of individually addressable RGB LEDs and a controller that can vary color and brightness (Figure 3).

Figure 3 NeoPixels contain both RGB LEDs and a controller, and can come as a multi-unit strip.
Data is sent to the first NeoPixel in a strip which, if not the intended addressed destination, forwards it to the next NeoPixel. In this way, one data line can talk to hundreds of NeoPixels. The micro’s data out line is connected to the strip’s DI input. That same data is also output from each NeoPixel’s DO line, which connects to the next NeoPixel’s DI input along with 5V and GND.
Now, for the phototransistors; I used the Everlight PT334-6C. Let’s first connect all the collectors together and ground the emitters. Also, let’s add a single resistor to the collectors and pull it to 5V. The collectors are now all connected to a single digital input line of the micro. When the NeoPixel in any empty silo is turned on, the combined collectors from the phototransistors will drop low; this status can then be read by the micro.
Next, let’s take a look at the schematic, just to be clear on what I’m suggesting (Figure 4).

Figure 4 This schematic summarizes the design techniques covered in the prior few paragraphs.
You can see from Figure 4 that we are now only using two data lines to sense the status of the 10 silos. This number could be 100 silos while still only using two data lines, for that matter. Keep in mind one caveat, however. The phototransistors have some collector leakage (dark current or ICEO). Using a 5V supply, the leakage will be approximately 10 nA at ~40° C ambient temperature. That means that our 10 phototransistors will have a combined leakage of around 100 nA.
Using a 3.3 kΩ pullup resistor means that the dark current will pull the micro’s data line down by about 0.33 mV. This current is low enough that it thankfully will have no effect on the detection of silo status. Even if we had 100 silos, the dark current would still be no more than 1 µA and only drop the data line by ~3 mV. What could have a greater effect is if the rods are not snug in the silos, for example, or the phototransistors are not in a light-tight enclosure. In either case, the resultant ambient light might increase leakage current. Ambient temperature variance from the assumed ~40° C also begs for consideration.
Now let’s look at how the micro uses the two I/O lines to sense the state of the silos. In general, what will be doing is turning on the NeoPixels from the first silo and then checking the state of the digital input from the phototransistors. If it’s low, the phototransistor pulled the line low, indicating that the silo was unoccupied (empty). After that we move to the next silo and repeat the operation, and so on. As in all things, however, there is a bit more necessary detail.
In this project, there was a need to keep the NeoPixel brightness down, since empty silos were visible, and we did not want to be able to see continuously flashing lights. So, in the Arduino C code we used the brightness setting in the NeoPixel library to set it low (brightness can be set from “0”, i.e., off, to “255”, full brightness). You can also set the color of the NeoPixel; I set red, green, and blue to all be on with equal brightness, resulting in the full spectrum of white light.
That all said, the NeoPixel’s brightness setting is not actually a brightness setting. It’s a perceived brightness setting. By setting brightness from 0 to 255 you are actually setting PWMs that are driving the RBG LEDs. So, when you set the brightness to 255, the LEDs are on continuously. When you set it to 50, the LEDs are on for 50/255, or 19.6%, of the time. The LEDs are actually still at full brightness but are perceived to be dimmer. There’s nothing surprising here; this is how LEDs are often dimmed. But, because we a trying to digitally detect this light in the phototransistor, we must take this perceived vs actual deviation into account.
Let’s work the numbers. The WS2812B PWM rate is between 400 and 800 Hz. That means that if it is 800 Hz the PWM sequence executes every:
1/800 = 1.25 ms.
This 1.25 ms is then broken up into 255 steps which means the shortest on-time is:
1.25 ms/255 = 4.9 µs
The 4.9 µs is the pulse width you would see if brightness is set to 1 and it would repeat 800 times per second.
The rise and fall time of the phototransistor is something less than 15 µs. This means we should not use a brightness setting below 4 (giving a 19.6 µs on-time) as we need time for the phototransistor to settle. So, to make sure we sample the phototransistor state fast enough, with this minimum on-time of 19.6 µs, let’s say we should sample every 2 µs. We should also keep scanning for a full PWM cycle which, for the 400 Hz number, is:
1/400 = 2.5 ms
From this information, we decided to test the phototransistor over and over, every 2 µs, and do it 1,250 times.
Now that we have our numbers, let’s look at a portion of the micro code.
#define LIGHT 0 // Used in checking photodetectors
#define LedScanDelay 20 // Microseconds delay when moving to a new silo
#define NumTestLoops 1250
#define TestLoopDelay 2 // Microseconds
SiloScanLeds.setBrightness(5); // Set the brightness
for (uint8_t n = 0; n < NUM_OF_SILOS; n++) {
SiloScanLeds.setPixelColor(n, WHITE); // Turn on silo LED[n] for sensing
SiloScanLeds.show(); // Update the silo LEDs
delayMicroseconds(LedScanDelay); // Do a slight delay for system to settle
siloStatus[n] = occupied; // Initialize flag before test
for (uint16_t i = 0; i < NumTestLoops; i++) { // Run a number of tests on silo n
if (digitalRead(DETECT_PIN) == LIGHT) { // Check phototransistor voltage to see if light is detected
siloStatus[n] = empty; // Mark silo as not occupied
}
delayMicroseconds(TestLoopDelay);
}
SiloScanLeds.setPixelColor(n, BLACK); // Turn off silo LED[n]
SiloScanLeds.show();
delayMicroseconds(LedScanDelay);
}
As you can see, the code loops through all the silos, and at each silo it executes 1,250 checks of the phototransistors’ status, which takes 2.5 ms. The appropriate delay is also applied after each check. If any one of the 1,250 phototransistors’ tests shows a digital low, the light has been detected and therefore the silo is tagged as empty.
After this, a 20 µs delay is applied to allow for the rise time of the phototransistor if a low was detected. Then, the outer loop changes to the next silo and starts a new set of tests. We call this code around every 500 ms to update the status of the silos. Using the brightness setting of 5 works well in the actual device. It has never failed a detection and appears very dim when looking down an empty silo.
Although this silo and rod project may be a bit of an esoteric design, the concepts shown to reduce the I/O requirements in an optical detection system can also be applied to other systems. Keep them your back pocket for consideration in future projects! For more information on NeoPixels, see a previously Design Idea in the Related Content section.
Damian Bonicatto is a consulting engineer with decades of experience in embedded hardware, firmware, and system design. He holds over 30 patents.
Phoenix Bonicatto is a freelance writer.
Related Content
The post Reducing I/O requirements for optical detection appeared first on EDN.


