I recently sent out an email update about some of the things I am going through with the design of this Computer Engineering for Babies, and omitted the technical details. But am including them here for people that want a little bit deeper of a technical dive.
So the way this book works is that there are 5 light sensors in the back page, and then there are little holes in the pages so that the light sensors are revealed as you open pages of the book. The book starts with no holes, and all the sensors dark, and with each page turn you end up revealing another light sensor until all the sensors are open on the last page. Someone at some point proposed I could get away with only using three sensors by using them in a binary configuration ie 000 for the first page, 001 for the second page, 010 for the third page, etc. However, this doesn't work because once you reveal a light sensor by turning a page, you cannot cover that light sensor on a later page. Essentially, you can only toggle each bit from 0 to 1.
The next part is that the sensors are not binary. We can't decide in hardware if a sensor is open or closed, we have to do that in software. The sensors are tuned to pick up small differences in light, and even if the book is completely closed, if yoru otside in the day time, some light is going to come through to those sensors. So if a sensor is 5% active we don't really have enough information to conclude that it's on or off. So what we do is basically read in all the sensor values, and then in the microcontroller code, we compare the differences in the light sensor values, and we look at what the highest and lowest values have been over the last 2-3 minutes, to make our best prediction at which page is currently open so we know how the buttons and LED should behave.
The light sensors are phototransistors, but you can think of them as resisters where the resistance is really high (1 giga ohm) when it's really dark, and the resistance drops low (1K ohms) when it's light. I then set up a basic voltage divider by putting a resister in series to ground. So the circuit looks like this:
With this configuration, in a dark room, the transistor has a really really high impedance and compared to the 1M, so the voltage between the two components, at the input_to_adc node is going to be very very close to 0. And when there is light coming in then the 1M ohm resister is going to be the much larger of the two, so the voltage at the input_to_adc node will be be close to 3.3 volts (or whatever the battery is).
This configuration worked no problem when using an Atmega 328pb. The 328PB is basically the same chip as the atmega328 which you would find in an Arduino UNO, and has a built in Analog to Digital Converter (ADC). This part of the chips takes an analog voltage and converts it to a digital number between 0 and 1024 that we can use in the code. For instance, if we have a voltage of 300 mV, and our battery is at 3.3 volts, then when we read the ADC, we would get a value of 93 (1024*0.3v/3.3v = 93). This all worked great on the 328pb chip.
But then, the 328pb became sold out everywhere, and my only option was to find a new chip. The Texas Instruments MSP430 G series (g2452 to be exact) is a very inexpensive and super low power chip. It's ideal for this kind of a project, and probably would have been a better choice from day one, but the Atmel chips were more familiar to me, so that's what I started with. I tried to just recycle the same voltage divider circuit with the new chip, but for some reason when getting down into values around 50 mV instead of getting ADC readings of 15, I was getting values around 29. This is bad because if you're reading the book in the evening with your child in their dimly lit room right before they go to bed, combined with the tiny wells that the light has to get through to get down through 5 pages to the light sensors in the back of the book, we need the full range of values. IE we might have 4 light sensors that have ADC readings around 1 and then one light sensor with a reading of 12, and we need to know that that means the book is open to the first page. If the ADCs are all reporting values of 29, then we're not going to be able to figure out which page is open.
It took a while to figure out why this was happening, and eventually I pinned it down to just being that these chips from TI are cheaper, and use cheaper ADCs and the margin of error is just higher. The data sheet says there is a max total unadjusted error of +/- 5LSB (least significant bits), but now that I am writing this down, I feel like I was seeing much larger than 5LSBs of error, maybe closer to 20. I am going to have to look into this some more. Anyway, we concluded that all of the error we were seeing was concentrated at the bottom rail close to 0 volts, so the idea to fix this is basically just to stay away from the rail.
The way to do this is to just add another resister in parallel with the phototransistor, so that when the transistor gets next to no light, instead the resistance going all the way to 1G, it maxes out at around 1M. This basically cuts our range in half. Now instead of getting values from 0 to 3.3v and 0 to 1024, we will only get from 1.65 to 3.3v and 512-1024.
The problem now is that we cut our resolution in half because our voltage range is being mapped to 512 through 1024 instead of 0 to 1024. This is actually no big deal for me. When I initially wrote the code I thought that the ADC only went up to 256, and when I realized it went to 1024, in order to get things working, I just divided all the values by 4 and went about my way. So I can remove a line of code and still double the resolution.
The next problem is that the battery isn't always at 3.3 volts. Sometimes it's at 3.2, sometimes it even dips down to 2.9 after a while. Also, resistors aren't perfect, and typically have 5% variance, so due to mismatched resistors, our floor isn't always going to be 1.65 volts. We may find it's 1.75 volts or 1.55 volts depending on how mismatched our resistors are. The trick here is that we need to be able to query the ADC without the transistor there to see where the new floor is.
My buddy Zack said typically you would have to populate half the board and run some sort of calibration code on the chip in order to store this value to memory before you put the transistor on the circuit. But that sounds awful and expensive. So what we do instead is tie the transistor to a GPIO pin of the microcontroller (sensorEnable). So we can configure the top node as an GPIO input (essentially removing the transistor from the circuit), and query the ADC to find our floor. Then we can configure the pin as an output in order to activate that leg of the circuit and query the light sensors. This way we don't have to store anything to memory, we don't have to make production a two step process, and we can re-calibrate as often as might be needed.
The other idea was to just take readings once every few hours, and then keep track of the lowest floor that we've had in the last 4 days or something. At some point all the sensors are bound to be completely dark, and we could find the true floor. This isn't as clever as the first idea, but I think it could work just as well.
The last problem was easily solved. I've maxed out all the GPIO pins already on the MCU, but fortunately, there is a 20 pin package that's only a few cents more than the 14 pin package that I was currently using.
Anyway, if you read this far, you're probably a smarter person than me; I don't think I would be read this far if I didn't experience this all first hand. So thanks for caring about the project, and I hope this was somewhat interesting.