Side-projects, successes and failures all in the same place!
Don't wanna be here? Send us removal request.
Text
NRF52840 Xiao Sense
Happy Manic Maker Monday!
For two upcoming projects, I'm going to be using two flavors of the Seeed Studios Xiao boards based on the NRF52840. These little boards are really something.
For one upcoming project, it has one of each of the exact things I need:
BLE to connect to another project on which I'm working
UART for chatpad input
SPI for eink display
I2C for a GPS
A few GPIO for a soft power button, an indicator LED, and maybe a vibration motor for haptics
Battery power and charging
An internal IMU (on the Sense flavor)
The only difference between the Sense and the non-Sense is that the former has a built-in IMU and microphone which are meant for tinyML projects since ML is all the rage (think of being in ultra-low power and performing inferences on audio samples but sleeping and collecting in-between).
In fact, for this Learn-a-Thon, there are a few things I totally want to investigate: the Embedded Template Library (for exploration on C++ to really object-orient the project), nRF Connect SDK, Zephyr OS (I'm late to jump on the bus here but there's good talk about this Linux-like take on RTOS), BLE, and TinyML.
nRF Connect SDK - It will be nice to get into the APIs provided by Nordic without any layers inbetween. A simple bare-metal eventloop will be lovely here.
ZephyrOS - I'll probably only be using this one of the projects since for the other one, bare metal will be just fine and I'd like to just get into the direct nRF Connect SDK for the hell of it. They say this abstracts away drivers which is an interesting prospect included in an RTOS! I've heard it's 80% configuration and 20% coding but, if it's good code running the drivers, why not just leverage it!
Embedded Template Library - generally I don't code in C++ besides for work, but I love the idea that it is possible to code something that uses primarily static data and has a low footprint. I've read reviews on ETL, heard about it on podcasts, and just gotta try it. There is a solid reason for using C++ in these projects and that is that I want to create a common base class that outlines capabilities of each of these devices.
BLE - I've done lots with wifi, written wifi drivers, wifi managers, connectivity managers, etc, but never BLE! I totally feel like I need to try it out! Especially since one of the projects is purely a sensor arm that transmits over BLE. Eventually, something else that would be really interesting would be OTA over BLE which I've never had to manage.
TinyML - I also haven't jumped into the ML/AI with both feet really. I've used pre-compiled models and hardware acceleration and some tangential TVM stuff but I want to create the model and see it with my own destructive hands >:) I'm also interested how much optimization is in control of the model developer.
Low Power Mode - I've done some of this already at work but it's a good idea to get in the weeds on a different micro. Especially, I'd like to understand how Zephyr might abstract this, how I can optimize packet offloads, etc.
I've got kind of a slew of experiments I want to try:
Zephyr OS Emulation - No hardware way to get started upper level programming. I would love to code around in Zephyr and get emulated results. Always a handy tool.
Zephyr OS - Get hello world on Xiao (sanity, familiarity, etc)
Zephyr OS + BLE - I've seen demos, I think it's easy to get started. Attaching a button or sensor should be pretty easy from there.
TinyML + IMU + BLE (https://blog.tensorflow.org/2021/05/building-tinyml-application-with-tf-micro-and-sensiml.html)
0 notes
Text
Self Hosting Rabbit Hole
I've long believed that one day someone will democratize the personal server. I think it's probably about time. The thing is, the world is highly shifting toward citizen coders since there has been a high demand for that specialty (even this guy who promised himself to never code after CS32 from UCLA turned out to join the software engineer's cult).
So folks are getting smarter about their own content. VPN services are widely advertised inbetween youtube videos to protect the formerlyunsecured torrenter of old, thwart the eyes of ISPs, and give access to local services; people are getting wary of "Big Tech" holding onto their personal bits and are understanding, the cost of free data is the content of the data itself; the cost per intelligent thing is going down and presence of a computer in ANYthing is exploding.
The time to strike is almost on us, where someone develops a very simple blackbox home server. I'd love to see it. The barriers to entry are really:
A spiffy UI
A framework for integrating several web services and their dependencies
Wrappers around common services
In fact, there are some frameworks popping up already with really decent UI. The tough part is the average consumer doesn't know what DDNS is or how to forward ports.
Maybe the real conceivable way to do it is to wrap the whole package with its own router (modularly?). From a hw perspective, I could see a multiprocessor device with one dedicated to networking to maintain uptime, manage ports and access around the house, then another few (or, like the TuringPi, something that can accept several slots) for serving.
In terms of the wrappers, no-ip has APIs to manage hostnames even.
It's definitely more than possible to cobble this together haphazardly and, though my middle name is "Cobble this Together Haphazardly", I haven't really dove into self hosting stuff until now (besides my old website that I used to host on a Pi).
For the reasons above, I've already started on exploring the wild worlds of nextcloud and photoprism. Once I replace iCloud and Gdocs (out of memory my ass!) which I'm totally loving (Photoprism has TensorFlow object detection so it knows context of pictures), I plan to explore Home Assistant a bit.
One thing's for sure - the power of being free from paying month-to-month for data is addictive. If more people felt empowered to own their data and have full control of it, I think it would be more beautiful world.
0 notes
Text
Getting the ESP8266 Code Ready for C
Espressif chips are the sexy mcu’s these days. If you’re reading this, it’s more likely because you’re already familiar with their with what they’ve got to offer. They made their first splash - and I mean like the bombest cannonball you’ve ever seen - with the esp8266 which explorers spent some good time opening the SDK and even going so far as decapping and tracing the silicon. The chip came as an oddly shaped module with 8 pins (the ESP8266-01) then exploded into a bunch of others.

I’m here to convince you to get one if you haven’t and experiment with me because there’s a world of possibility with these. If qualitative statements don’t do it for ya, then check out these specs mostly from its wikipedia entry:
32-bit CPU (Tensilica Xtensa L106 operating at 80MHz
64KiB of instruction RAM and 96KiB of data RAM
802.11 b/g/n wifi with WPA/WPA2, WEP, and open security
2 GPIOs exposed to us (their MUXed functions aren’t great-GPIO 0 is muxed to SPI chip select and GPIO 2 is muxed to I2C SDA... ^^; buuut...)
Each GPIO can be configured for pull-up, can trigger interrupts, be configured as open drain or push-pull. Reportedly (from the datasheet), we can also do PWM but it says in another breath that it is “software programmable” which I take to mean DIY.
500kB or 1MB external SPI flash on the module (not on the SoC)
Afraid you’re going to be spending too much to get all that? YOU CAN GET 4 FOR $14.00 SO NO EXCUSES!
Of course not all of the pins are exposed so that stinks and means we don’t get all of the functionality but we’ll see what we can do with what we’ve got and maybe I’ll do another blog entry if that’s what we’d like to do.
Anyway, I love these and think you should too. I wanted to share how to get as close to main() as possible, and how to make your own tiny programming jig so....let’s GO!
Hardware
Can’t start the software without a little hardware! We need to make a quick programming jig to move between “serial programming” and “run” mode.
I used parts-box parts but you can get similar parts from almost anywhere. Just in case, here’s an ingredients list:
3.3v FTDI cable/module
ESP8266-01
Push button switch (for the reset line which is active low. You’ll see I used a mouse button for some reason but if you use a push button, you’ll need a pull up resistor)
A latched push button (for power but I used another mouse button)
DPST/DPDT switch
Headers (both female and male)
Some perf board
Here are the pin configs for serial programming:
and so this is the schematic we’re after:
Again, I only had the parts I had so here’s the schematic with what I had:
And here’s a quick map of how the perfboard is laid out:
(The boxes and blue lines represent top-layer components and wires, the red lines represent solder mazes)
(Here’s the back side so you can basically put the parts in as in the first photo and solder the traces on the back like this photo)
When all is said and done, you end up with this:

Easy as pie and hardware part done!
Software
Alright, so next up let’s get the toolchain setup, upload the blinky code, modify, and upload again so we feel good and ready to move onto projects. I chose open-esp-sdk purely because others really liked it.
This process is pretty typical but here’s the idea:
Install Dependencies (apt-get) -> Clone the Repository (git) -> Build the Project (make) -> Globalize the Tools (assigning to a global)
Then it’s time to build the example and load it up on the ESP.
Build Blinky (make) -> Power Cycle the device in program mode -> Load the new binary to the board (esptool.py) -> drink beer in celebration
Setting up the Environment
Install the Dependencies - download and install all the programs that will help you build the environment, build the esp binaries and use the upload tools
sudo apt-get install make unrar-free autoconf automake libtool gcc g++ gperf flex \ bison texinfo gawk ncurses-dev libexpat-dev python-dev python python-serial \ sed git unzip bash help2man wget bzip2 libtool-bin
Clone the repository - for those who are new to git (many at my company were when they came aboard, obviously me included) all this is is just copying a project that someone has posted to git down to your computer. Make sure you go to the directory where you want your project to reside and type:
`git clone --recursive https://github.com/pfalcon/esp-open-sdk.git`
Build the Environment - When it’s done, cd into the esp-open-sdk directory and make the project
cd esp-open-sdkmake STANDALONE=y
The “STANDALONE=y” ensures you get the xtensa toolchain and the esp SDK.
Depending on your hardware this may take a while. Just for shits and giggles I installed it on my Crubuntu laptop which took an hour (to be fair I was watching netflix on the Chrome side). If it goes off without a hitch, it will report
Espressif ESP8266 SDK is installed, its libraries and headers are merged with the toolchain
Pitfalls
I’ve encountered two errors when I tried this though - one was when I followed the github instructions and didn’t try to install libtools-bin (since it said you may not require it). For safety, I just put it in my apt-get command above.
The other was because I had played around a bunch on my computer and had the LD_LIBRARY_PATH variable set to which it complained
[ERROR] Don't set LD_LIBRARY_PATH. It screws up the build.
If that happens, make sure you clear your LD_LIBRARY_PATH environmental variable with
unset LD_LIBRARY_PATH
and try again.
Globalize the Tools - We’ll want to make it so that we can access the xtensa gcc toolchain (which we’ll use to compile link and convert our sourcecode to ELF) and the esptools (which we’ll use to convert to a binary and write to flash locations on the chip over serial) everywhere. To make that happen, we need to add the path to xtensa-lx106-elf-gcc and esptool.py to the ~/.bashrc - the file that loads every time we load a terminal session.
There should have been a line above the success message that says Xtensa toolchain is built, to use it:
export PATH=[your_path_to_esp_open_sdk]/esp-open-sdk/xtensa-lx106-elf/bin:$PATH
Go ahead and do that - type:
export PATH=[your_path_to_esp_open_sdk]/esp-open-sdk/ xtensa-lx106-elf/bin:$PATH
But let’s also add it to our ~/.bashrc which controls what happens when we open a new terminal:
echo “export PATH=[your_path_to_esp_open_sdk]/esp-open-sdk/ xtensa-lx106-elf/bin:$PATH” >> ~/.bashrc
and then we’re good! To test if that worked, cd to the esp-open-sdk directory and then do
cd examples/blinky
and type
make
If that went through well (i.e. it didn’t throw any complaints to you), you’ve properly configured your environmental path variable. Now remove the blinky binaries (make seems to make different binary locations than those used in make flash and make clean)
rm -f blinky blinky.o blinky-0x00000.bin blinky-0x10000.bin
Testing with blinky.c
Our next goal is to run blinky on the board.
Test the Serial Port
First let’s make sure we have access to the serial port so we can program the device. Connect your FTDI programmer to your USB port and type
dmesg | grep FTDI
It should list a line like this to tell you which /dev the FTDI connected to like
usb 1-2: FTDI USB Serial Device converter now attached to ttyUSB0
Next, send something like (where ttyUSB0 is the device you got)
echo -ne “\r\n” > /dev/ttyUSB0
If you just get another prompt pop up immediately after, you shouldn’t have anything to worry about and can move to the next section. If you get the Permission Denied message, you need to add yourself to the same group as your FTDI port. Let’s find out which group we need to be a part of. Type:
ls -l /dev/ttyUSB0
and you should get something like:
crw-rw---- 1 root serial 188, 0 Jun 19 02:52 /dev/ttyUSB0
Many sites online just say to add to “dialout” but you see that my port is connected to the group “serial.” I can add myself to that group with:
sudo usermod -a -G serial alexmo
and then log out and log back in.
Now when you’re back in, if you retype
echo -ne “\r\n” > /dev/ttyUSB0
you shouldn’t see the permission denied message.
Build
Build with
make
You’ll see a few new files. The binaries marked blinky-0x00000.bin and blinky-0x10000.bin tell you where they should be written.
Program
Now let’s program the device with the binaries. If you see blinky-0x00000.bin and blinky-0x10000.bin, just type
esptool.py write_flash 0 blinky-0x00000.bin 0x10000 blinky-0x10000.bin
It will work on it:
esptool.py v1.2 Connecting... Auto-detected Flash size: 4m Running Cesanta flasher stub... Flash params set to 0x0000 Writing 36864 @ 0x0... 36864 (100 %) Wrote 36864 bytes at 0x0 in 3.2 seconds (91.5 kbit/s)... Writing 196608 @ 0x10000... 196608 (100 %) Wrote 196608 bytes at 0x10000 in 17.1 seconds (92.1 kbit/s)... Leaving...
and then start blinking!
0 notes
Text
Crash Course
Wowza. It’s been a quiet blog season since nabbing a job at Ring. It’s a helluva product and I definitely drank the coolaid...SO GO OUT AND GET A RING DOORBELL! ALWAYS BE HOME!
Anyway, it’s been a bit of a crash course in how to get shit done, how to learn, how to code and how to interact with humans (Luckily they’re not on to me...yet....)
Anyway, I’ve met some very cool and smart people so far - wizards of hardware, software, security, web API’s, agile and the like -- many of which I snubbed before but now appreciate in a big way.
With this new knowledge and these new experiences, I’m scrapping the old 3d printer project for two new projects - a bluetooth watch (cuz you HAVE to do a bluetooth watch) and a new 3d printer project with a better selected chip. Both will use chips I’m not too familiar with.
The purpose of the watch project is just to gain understanding of a beginning-to-end prototype. I’ll use a chip I’ve never used before, make a dev board that basically has all the things I want to try on it, and then will cobble it together with case fab and all. I also really want to learn all about BLE.
The 3d printer project is because I got so damn far using just an arduino and an MSP that I feel dumb for not finishing before. Also, I went to CES this year and listened to the Embedded podcast and I LOVED the spunk of Cypress semiconductor and the capabilities of their chips so I really want to give their PSoC 4 a go (the dev board was only $4.00!). It will be able to handle the analog front end as well as the digital control pretty well. I’ve seen the IDE and it looks waaay too simple so we’ll see how fast the firmware gets done on this. One of the cool things is that it’s somewhere between an FPGA and an MCU so you can offload a lot to hardware which will be interesting to see.
So huzzah to being home again (despite being Always Home)!
0 notes
Text
Side Trip: Traversing an Array of Structs
Hi y’all. Quick updates:
The very thermistor I was testing in the last few posts was working swimmingly (I was rocking the accuracy!) but then I tried the thing under water, neglecting that it was at some potential. I saw some bubbles pop out of the thermistor casing but thought nothing of it. Down the line, I retested and it was dead as a door-nail: the R25 had doubled for some reason and, though I tried to recalibrate, there was a drift of several hundred ohms per degree. Nuts.
I reordered some, got the wrong package type and will order again from Mouser (they’ve got a very reasonably priced ARM M0 with built in bluetooth I’m dying to try to make into a project) but, in the meantime, I got to programming the interface.
For this, I’ll be using a standard 2x16 character LCD with an 8-bit serial to parallel shift register, and a couple of libraries I’m programming up myself. I saw there’s already a library for this particular SR, but sometimes you need to reinvent the wheel to learn.
For the interface, I wanted to make the menu creation modular so I threw it all into a class and here was my implementation:
Each menu item is a struct object (creatively named “menuItem”) which are organized into an array just to join em together.
Each menuItem has three attributes: char text[16], and two menuItem pointers to the previous and next menuItem.
So I created three menuItem arrays to test:
The next pointer of the “Open SD Card” menuItem should point to “File 1.” Then all of the file items should point back to the “Open SD Card” object from the prev pointer.
Next you similarly link the settings array and the settings object.
Here’s the ideal next assignments where X0, Y0, and Z0 are some points in memory assigned by the computer and where there was space to sequentially stick the rest of the array. The “address” of an array is always the address of its first element. If you increment that address, you get to the next element in the array (for example X0 + 1 = X1)
and here are how the previous assignments should work:
All of the OPEN SD CARD ARRAY menuItems should point back to that Y0 in the BASE ARRAY, and then all SETTINGS ARRAY items should have their prev pointer go back to Y1 in the BASE ARRAY. You have to go through each item in OPEN SD CARD ARRAY to assign their prev pointers but, since this is a fresh-baked home-made struct, you don’t have a .size() function to play with.
So you’re stuck working that out yourself. “Aha!” thought I, as I furiously typed out the two lines of code you see below, “I have the first element of each array, so all I have to do is go X0 in the OPEN SD CARD ARRAY and count up, checking if next == NULL. When it doesn’t any longer, I will have shot past the end of the array and therefore have finished assigning prev pointers!”
And so I did with the OPEN SD CARD ARRAY, then the SETTINGS array but no matter what happened, whether I was in the OPEN SD CARD ARRAY submenu or the SETTINGS ARRAY submenu, whenever I pushed back, it would take me to the SETTINGS menuItem or Y1. What was happening?
Well, like I said, the computer assigns space for the arrays I chose so that they can link up and go from one address to the next. I had assumed that these “random” locations would be far away from each other but, really, there’s nothing stopping the computer from setting up the two like this:
Here, you can see the memory address of the OPEN SD CARD ARRAY is right after that of the SETTINGS ARRAY so, as my for loop advances, checks for a NULL next pointer, then assigns the prev pointer, it runs from Z0 - Z1 - Z2 - Z3(X0) - Z4(X1) - Z5(X2) and assigns all of the prev pointers to Y1.
Moral of the story? I dunno. Don’t assume anything I guess, don’t overestimate the size of your memory on an MCU, and Google Drawings rocks as an illustrative platform?
Really, I just thought this was a pretty awesome lesstake!
0 notes
Text
How to make a Glorified Thermometer - Part Dos - Curve Fitting
Alright, friends, in our last episode, we took measurements of resistances that correspond with certain temperatures. This is always the first step of calibration: you need to find out what the relationship might be between whatever changes on the sensor, be it voltage or resistance, and the value we want to measure - in our case temperature.
Now we end up with a curve like this:
And would like to find a good approximation, with emphasis on the “good.” Engineering always involves trade-offs and, in this case, we might be able to get a crazy precise model but our little Arduino would have to chug through a lot of math. So what we’re looking for is a reasonably good fit.
Visual Evidence:
First let’s flip it. We were finding what Resistance corresponded to a known Temp, but ultimately we want the opposite - we’ll be measuring resistance, running some math and getting temp out.
There, that’s better. Okay, the first thing we see is that, as resistance gets smaller, temperature blows up. In fact, the resistor will fail before it ever reaches 0 ohms, so intuition might tell us we’re dealing with one of two things: either ln(R) or f(R)/R. We see no other poles so we know it’s not much more complicated than that our independent variable should take one of those forms.
Once we assume one of those as our independent variable, we’ll be able to use least square error to determine a polynomial that will fit this set of data. We could also use Excel/Google Sheets’ curve fitting tool after we determine the form of the equation to expedite, but I’ll go through the motions of the least-square error so that we have the power to use it if we need to in the future!
Okay, on to that equation.
T=f[R]/R
Let’s start by asking what if it’s of the form f(R)/R=T.
If a good model is of this form then we can just find T*R=f(R) and determine f(R). Here’s what T*R vs R looks like:
Amazing right!? Almost linear, r^2=1 let’s dust of our hands and call it done!
Well not quite...
Whenever we have a lot of features close to zero, it’s a good idea to plot the log of the axis where that features is measured. For example, in the case of our resistance vs temperature curve, we have a lot of change occurring when R is very small when temperature shoots up.
Plotting log(R)...
It turns out we’re diverging from the temperature when resistance is very small by using the f(R)/R formula. In fact, its max deviation occurs when resistance is smallest at 6% error which is a 34 degree K difference.
So what if we try ln(R)? Will we get better error?
T=f(ln[R])
So, I should have mentioned this earlier. There’s already an equation. It’s called the Steinhart-Hart equation and it goes a little something like this:
It implies that there is already a dependence on ln(R) and that we only have to go to up to a third order polynomial to get good accuracy. We want to put it through its paces though to make sure we’re hitting the best accuracy we can get.
First, just create a new set of data: 1/T’s and ln(R)
You can’t tell Excel or Google to exclude the 2nd order term and optimize so we have to do it by Least Squares ourselves. I’m posting a quick primer below to remind/entice interested parties. Otherwise, just skip the lower box.
Least Square Error
First, how about some intuition for newcomers/people who are used to just plugging and chugging (agh! poison to my ears!)
The idea is you have your variables (in this case, let’s say 1/T and x=ln(R)) and you assume that they follow a model
where y is the result of the model for a given ln(R), and 1/T is the experimental 1/T.
So then, the preliminary error between these two would be error=1/T-y
We can find out how good a model is by adding the 1/T-y for each ln(R). If that error is high, then that’s bad. The only problem is some y’s will overshoot the experimental 1/T and some will undershoot it so our error will be positive sometimes and negative at others which will cancel and lessen our sum of errors which is not good! So we square the error in order to only get positive values so really our equation becomes
Now’s the fun part: finding the variables! You want to decrease that error for each of the “design variables” A, B, and C so we take the derivative with respect to each one to find zero points (we have no trouble with accidentally getting a maximum since there’s not really such thing as maximum error here).
These are pretty easy solves thanks to the chain rule:
From here, we can rearrange to get a matrix form of the three equations:
or...
From here, conceptually, the answer is simple: multiply both sides by the inverse of the 3x3 matrix and get an equation for the coefficient vector.
I was on my way to calculating the inverse but, let me tell you friends, knowing the simplicity of the final answer, and seeing how long and dark that road of calculating the inverse, I think it’s best to let Wolfram finish up here. So here’s a semi-proof. They don’t so much simplify until they get the inverse, but show its equivalence to the result that we achieved which may be good enough for some: http://mathworld.wolfram.com/LeastSquaresFittingPolynomial.html
Okay hopefully all of that makes sense- the goal is to minimize the sum of residuals squared. The way to do that is to solve for a vector of coefficients such that the derivative of the square residuals are 0 representing a minimum error along the A, B, and C “directions.”
So the solution to our problem is a stone’s throw away:
For vectors and matrices
the solution to a becomes:
So we fire up Octave and store our ln[R] values into a vector x and then create the 3 x n matrix X = [x.^0 x.^1 x.^3], then calculate a=(X’*X)^-1*X’*y.to get coefficients of the Steinhart-Hart model.
Next I did the same with a third-order polynomial that included the x^2 term, and then a final third-order that used straight T rather than 1/T.
Here are the results:
And here are the maximum percent errors and the absolute error, assuming that the error occurred at our points of interest at the higher temperatures:
There was that little jagged bump in the middle where it looks like I didn’t do a great job taking measurements, but I think the winner is clearly the third order polynomial with relation to plain ol’ T. Toward the upper end we even have a better fit and the great thing is the math designated it so!
Last Thoughts on the Model
PHEW!
Hopefully that was a good reminder of the power of math. If I were to do it again, I might try to get higher temperature measurements for calibration and to take it a little slower so that the data is more crisp.
Also, I might try to do some weighted least squares where we put more emphasis on decreasing the error on the upper temperatures and allow the curve to fit more loosely on lower temps.
Overall though, I’m pretty confident in the model we found!
Next up: How to Make a Glorified Thermometer: Part the Third - Picking a Resistor from the Bunch (Picking the Second Resistor)
1 note
·
View note
Text
How to make a Glorified Thermometer - Part 1 - Measurement
There comes a time when you have a simple sensor meant to translate some stimulus into a change in resistance.
...and so you build a voltage divider stick it into your micro and say “ah, finished.” Right?
Unfortunately, I have never had this experience. Let’s engineer the hell out of this thang!

The Sensor in Question
The sensor in question is the thermistor used in the J-head reprap hot end for the Mark IV.

I’ve been working slowly on a 3D printer on the side made from one piece of plywood (and hand tools. Seriously, where is everyone getting their laser-cut parts these days??)
The sensor is the one that comes in the print-head. I’ve read about how the leads will short with the aluminum block so you end up having to do your own shrink tubing on the leads so I figured, if I’m going to have to take it out anyway I might as well calibrate it!
It’s a thermistor which means that, as temperature increases, the resistance changes as well. The change can be either positively or negatively proportional which they call a “Positive Temperature Coefficient (PTC)” or “Negative Temperature Coefficient (NTC)” thermistors. To be honest, I’ve only ever come across NTCs.
I retrieved the part number from the RepRap wiki, this is our thermistor (although, it’s not in the blob form-factor as the drawing would lead you to believe):
Keep this handy because we’ll need some of those numbers in later sections.
Intro to Calibration
Calibration is all about finding a mathematical function that will give us what we want from what we have. For example, we will get a changing resistance from this thing. How can we turn that into temperature?
First we need data! Grab two multimeters (one with a thermocouple) and follow along!
Take Measurements The first step is to gather data because we don’t know how the thermistor will respond to temperature changes. The datasheet does list datapoints but slight manufacturing errors make deviations so we can trust no one!
Get out two multimeters and set one up for temperature measurement and the other for resistance measurement.
On the temperature multimeter, connect the temperature probe. On the resistance multimeter, clip the probes to the thermistor.
Fill a pot with some water and stick it on the stove.
Put the temperature probe and thermistor into the water.
Get out your cellphone cam and start recording the faces of both multimeters and turn on the stove.

A neat packet of probes is a good idea. That way the temp probe is always close to the thermistor. Mine packet didn’t fit in the little hole in the lid of my pot so I abandoned this method

Get used to this image. I took about 20 min of footage and watched back in slow-mo to get all of the datapoints! Some day I will make “Recordo-bot” who will take datapoints for me...

I lovingly call this pot “The Calibrator”
You want to get are as many values between room temperature water up to boiling. We want to use F because, though all of our math will be in celcius or kelvins, there’s a little under 2 degrees F for every degree C so better resolution!
After that, repeat the same steps with just some canola oil. We do this because the water’s going to struggle as we get toward boiling. Meanwhile, the canola oil will raise temperature rapidly through the low temps and will allow us to measure up to our 400 degree F ceiling.
THE KEY HERE IS TO GO SLOWLY AND TO WORK SAFELY.
If you have solder joints, typical melting temperatures for solder are around about 361 F (says a quick google search) so just make sure they are far enough away from the solder that they aren’t affected.
Excel-ify those Measurements Okay, now go through the trouble of putting all of those resistances corresponding with each degree into excel and plot that sucker. Typically, over small ranges, we would use a first order equation to represent temperature changes but it’s clear that a line is a poor approximation.
Data! The dark blue is our experimental measurement, the light blue is a not very good linear model
So what is a good approximation?
Find out next time on the next exhilarating episode: “How to make a Glorified Thermometer - Part Dos - Curve Fitting”
0 notes
Text
The other half...
Alright, I'm trying with all my might to not do a TTE: Xbox Chatpad (but I'm weak so it WILL come out in all of its verbosity). In the meantime, here's some code to get you started interpreting Chatpad inputs to use it on our modular keyboard.
So already, we know that we can essentially pick hex values, feed them parallel-ly to our MUX network, and get a keypress.
The next step is to pick an input device. I chose the Xbox chatpad for its awesome feel.
If you have a chance, go to Cliffle's blog for pinout and protocol for these things. It was essential in my figuring out how to use the chatpad!
Basically, we have an Initialize command, and an Awake command (and that's all we need to send!). The chatpad is sleepy and...well...chatty so it will fall asleep every second or two and will often send messages so you'll 1. have to wake it up every second (or less) and 2. ignore the garble and listen only to the keypress responses.
The keypresses are the only ones that begin with 0xB4 and then the actual key, if reading one at a time, can be found coded into the 5th byte in an 8 byte frame.
I made up a table to show the fifth byte codes.
Set the thang up to 19.2kbps UART, and you can figure out what has been pressed by ignoring anything that doesn't begin with 0xB4 and reading the 5th byte of everything that does. Here's a demo program for making one of the launchpad LEDs flash for an 'A' keypress and the other for a 'B' keypress that I wrote.
I know making LEDs light with button presses are like...preinstalled onto the launchpad, but when you're doing it using UART and a chatpad and you know how it works? All worth it.
#include "msp430g2553.h" #define RED BIT0 #define GREEN BIT6 #define TXGP BIT2 #define RXGP BIT1 unsigned char txMsgBuffer[]={0x87,0x02,0x8c,0x1F,0xCC}; unsigned char msgCounter=0; unsigned char rxLength=0; unsigned char readingKey=0; unsigned char modifier=0; unsigned char firstKey=0; unsigned char secondKey=0; unsigned char prevMod=0; unsigned char prevFirst=0; unsigned char prevSecond=0; unsigned char tempRX=0; int main(void){ //Stop the watchdog timer WDTCTL = WDTPW + WDTHOLD; //Set default timer DCOCTL = 0; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; //Set output GPIOs to low P2DIR |= 0XFF; P2OUT = 0X00; //Set up UART mode for P1SEL |= TXGP + RXGP; P1SEL2 |= TXGP + RXGP; //Set output GPIOs on P1 to 0 P1DIR = RED + GREEN; P1OUT = 0; //Set up the clock and prescaler to baudrate of 19200 UCA0CTL1 |= UCSSEL_2; //UART ctrlled by SMCLK UCA0BR0 = 52; UCA0BR1 = 0; UCA0MCTL &= ~(UCBRS2 + UCBRS1 + UCBRS0); //Start UART UCA0CTL1 &= ~UCSWRST; //Enable UART interrupts UC0IE |= UCA0RXIE; //Establish timer CCTL0 = CCIE; TACTL = TASSEL_2 + ID_3 + MC_1; CCR0 = 62500; //Low Power Mode with global interrupts enabled. __bis_SR_register(GIE); //Send initialize message to chatpad msgCounter=0; UC0IE |= UCA0TXIE; while(UC0IE & UCA0TXIE){ P1OUT |= RED; } P1OUT &= ~RED; txMsgBuffer[3]=0x1B; txMsgBuffer[4]=0XD0; // __bis_SR_register(CPUOFF); while(1) { if(firstKey==0x37 || secondKey==0x37) P1OUT |= RED; if(firstKey==0x42 || secondKey==0x42) P1OUT |=GREEN; if(firstKey!=0x37 && secondKey!=0x37) P1OUT &= ~RED; if(firstKey!=0x42 && secondKey!=0x42) P1OUT &= ~GREEN; } } //on sending a byte #pragma vector=USCIAB0TX_VECTOR __interrupt void USCI0TX_ISR(void) { UCA0TXBUF = txMsgBuffer[msgCounter]; msgCounter++; if(msgCounter==5) UC0IE &= ~UCA0TXIE; } //A5 = 1010 0101 //41 = 0100 0001 //C3 = 1100 0011 //B4 = 1011 0100 //on receiving a byte #pragma vector=USCIAB0RX_VECTOR __interrupt void USCI0RX_ISR(void) { unsigned char temp=UCA0RXBUF; if(rxLength<=0){ switch(temp){ case 0xA5: rxLength=8; readingKey=0b10; break; case 0x41: rxLength=12; readingKey=0b10; break; case 0xC3: rxLength=4; readingKey=0b10; break; case 0xB4: rxLength=8; readingKey=0b11; break; default: readingKey=0; break; } } else if(rxLength>0 && (readingKey&0x01)){ if(rxLength==5) {modifier = temp;} else if (rxLength==4) {firstKey = temp;} else if (rxLength==3) {secondKey = temp;} else if (rxLength==1) { txMsgBuffer[0]=modifier; txMsgBuffer[1]=firstKey; txMsgBuffer[2]=secondKey; msgCounter=0; UC0IE |= UCA0TXIE; } } if(readingKey & 0b10) rxLength--; } //every ~0.5 seconds #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A (void) { msgCounter=0; txMsgBuffer[0]=0x87; txMsgBuffer[1]=0x02; txMsgBuffer[2]=0x8C; UC0IE |= UCA0TXIE; }
You'll notice the strategy:
Send "Initialize"
Send "Awaken"
Read any received bytes
Keep sending "Awaken" periodically
And last but not least, outputting a byte to the Port 2 GPIOs by combining the above setup code with a look-up table, we can get our MUX network talking with the Chatpad
#include "msp430g2553.h" #define RED BIT0 #define GREEN BIT6 #define TXGP BIT2 #define RXGP BIT1 static const unsigned char gameToMux[]={ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0-16 : nothing 0xAF,0X0F,0X0C,0XAC,0XAE,0XB8,0XB0, //17-23 : 7 6 5 4 3 2 1 9,9,9,9,9,9,9,9,9, //24-32 : nothing 0XCF,0X4F,0X4C,0XCC,0XCE,0XD8,0xD0, //33-39 : u y t r e w q 9,9,9,9,9,9,9,9,9, //40-48 : nothing 0XEF,0X6F,0X6C,0XEC,0XEE,0XF8,0XF0, //49-55 : j h g f d s a 9,9,9,9,9,9,9,9,9, //56-64 : nothing 0X2F,0X2C,0X8C,0X8E,0X98,0X90,0x09, //65-70 : n b v c x z 9,9,9,9,9,9,9,9,9, //71-80 : nothing 0X26,0x8F,0X8A,0X64,0X28,0x09,0x09, //81-85 : rgt m . spc lft 9,9,9,9,9,9,9,9,9, //86-97 : nothing 0x09,0X8D,0XE8,0XC2,0XA2,0XAA,0XAD, //98-103 : , ent p 0 9 8 9,9,9,9,9,9,9,9,9, //104-112 : nothing 0X45,0XEA,9,9,0XCA,0XCD,0XED //113-119 : bkspc l o i k }; static const unsigned char gameToMuxSquared[]={ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0-16 : nothing 0xAF,0X0F,0X0C,0XAC,0XAE,0XB8,0XB0, //17-23 : 7 6 5 4 3 2 1 9,9,9,9,9,9,9,9,9, //24-32 : nothing 0xAF,0X0F,0X0C,0XAE,0X09,0XB8,0XB0, //33-39 : u y t r e w q 9,9,9,9,9,9,9,9,9, //40-48 : nothing 0X62,0X22,0X62,0X4D,0X42,0XF8,0X10, //49-55 : j h g f d s a 9,9,9,9,9,9,9,9,9, //56-64 : nothing 0X8D,0X82,0X02,0X8E,0X98,0X10,0x09, //65-70 : n b v c x z 9,9,9,9,9,9,9,9,9, //71-80 : nothing 0X26,0x8F,0X22,0X64,0X28,0x09,0x09, //81-85 : rgt m . spc lft 9,9,9,9,9,9,9,9,9, //86-97 : nothing 0x09,0XE2,0XE8,0XA2,0XA2,0XAA,0XAD, //98-103 : , ent p 0 9 8 9,9,9,9,9,9,9,9,9, //104-112 : nothing 0X45,0X4D,9,9,0XAA,0XAD,0X42 //113-119 : bkspc l o i k }; static const unsigned char gameToMuxCircled[]={ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, //0-16 : nothing 0x09,0X09,0X09,0X09,0X09,0X09,0X09, //17-23 : 7 6 5 4 3 2 1 9,9,9,9,9,9,9,9,9, //24-32 : nothing 0xAF,0X0F,0X0C,0XAE,0X09,0XB8,0XB0, //33-39 : u y t r e w q 9,9,9,9,9,9,9,9,9, //40-48 : nothing 0X62,0X22,0X62,0X4D,0X42,0XF8,0X10, //49-55 : j h g f d s a 9,9,9,9,9,9,9,9,9, //56-64 : nothing 0X8D,0X82,0X02,0X8E,0X98,0X10,0x09, //65-70 : n b v c x z 9,9,9,9,9,9,9,9,9, //71-80 : nothing 0X26,0x8F,0X22,0X64,0X28,0x09,0x09, //81-85 : rgt m . spc lft 9,9,9,9,9,9,9,9,9, //86-97 : nothing 0x09,0XE2,0XE8,0XA2,0X09,0X09,0X09, //98-103 : , ent p 0 9 8 9,9,9,9,9,9,9,9,9, //104-112 : nothing 0X45,0X4D,9,9,0XAA,0XAD,0X42 //113-119 : bkspc l o i k }; void outputKey(unsigned char FK){ if(modifier } // 0X78 - '<' // 0x4D - ']' unsigned char txMsgBuffer[]={0x87,0x02,0x8c,0x1F,0xCC}; unsigned char msgCounter=0; unsigned char rxLength=0; unsigned char readingKey=0; unsigned char modifier=0; unsigned char firstKey=0; unsigned char secondKey=0; unsigned char prevMod=0; unsigned char prevFirst=0; unsigned char prevSecond=0; unsigned char tempRX=0; unsigned char modArmed=0; unsigned int wasHeld=0; unsigned char capsLock=0; int main(void){ //Stop the watchdog timer WDTCTL = WDTPW + WDTHOLD; //Set default timer DCOCTL = 0; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; //Set output GPIOs to low P2SEL &= ~(BIT6 + BIT7); P2DIR = 0XFF; P2OUT = 0X00; //Set up UART mode for P1SEL |= TXGP + RXGP; P1SEL2 |= TXGP + RXGP; //Set output GPIOs on P1 to 0 P1DIR = RED + GREEN; P1OUT = BIT3; //Set up the clock and prescaler to baudrate of 19200 UCA0CTL1 |= UCSSEL_2; //UART ctrlled by SMCLK UCA0BR0 = 52; UCA0BR1 = 0; UCA0MCTL &= ~(UCBRS2 + UCBRS1 + UCBRS0); //Start UART UCA0CTL1 &= ~UCSWRST; //Enable UART interrupts UC0IE |= UCA0RXIE; //Establish timer CCTL0 = CCIE; TACTL = TASSEL_2 + ID_3 + MC_1; CCR0 = 62500; //Low Power Mode with global interrupts enabled. __bis_SR_register(GIE); //Send initialize message to chatpad msgCounter=0; UC0IE |= UCA0TXIE; while(UC0IE & UCA0TXIE){ P1OUT |= RED; } P1OUT &= ~RED; txMsgBuffer[3]=0x1B; txMsgBuffer[4]=0XD0; // __bis_SR_register(CPUOFF); while(1) { //if shift key is down... if(modifier==1){ //if shift wasn't already toggled... //if shift was already toggled before... if((P1OUT&RED)){ //turn on the "wasHeld" marker wasHeld=1; modArmed=0; P2OUT = gameToMux[firstKey]; } else if(~(P1OUT&RED)){ //turn on shift P1OUT |= RED; modArmed=1; P2OUT = gameToMux[firstKey]; } if(capsLock){ capsLock=0; P1OUT &= ~RED; __delay_cycles(10000); P1OUT |= GREEN; } } else if(modifier==5){ capsLock=1; P1OUT |= RED; } else if(modifier==2){ //if shift wasn't already toggled... //if shift was already toggled before... if((P1OUT&RED)){ //turn on the "wasHeld" marker wasHeld=2; modArmed=0; P2OUT = gameToMuxSquared[firstKey]; } else if(~(P1OUT&RED)){ //turn on shift P1OUT |= RED; modArmed=1; P2OUT = gameToMuxSquared[firstKey]; } } else if(modifier==0){ if(wasHeld && firstKey!=0){ //if just releasing from a held shift P1OUT &= ~RED; //turn off the shift wasHeld=0; //reset held to not held P2OUT = gameToMux[firstKey]; } else if(modArmed && firstKey!=0){ if(modArmed==1) P2OUT = gameToMux[firstKey]; else if(modArmed==2) P2OUT = gameToMuxSquared[firstKey]; __delay_cycles(10000); modArmed=0; P1OUT &= ~RED; } else P2OUT = gameToMux[firstKey]; } } } //on sending a byte #pragma vector=USCIAB0TX_VECTOR __interrupt void USCI0TX_ISR(void) { UCA0TXBUF = txMsgBuffer[msgCounter]; msgCounter++; if(msgCounter==5) UC0IE &= ~UCA0TXIE; } //A5 = 1010 0101 //41 = 0100 0001 //C3 = 1100 0011 //B4 = 1011 0100 //on receiving a byte #pragma vector=USCIAB0RX_VECTOR __interrupt void USCI0RX_ISR(void) { unsigned char temp=UCA0RXBUF; if(rxLength<=0){ switch(temp){ case 0xA5: rxLength=8; readingKey=0b10; break; case 0x41: rxLength=12; readingKey=0b10; break; case 0xC3: rxLength=4; readingKey=0b10; break; case 0xB4: rxLength=8; readingKey=0b11; break; default: readingKey=0; break; } } else if(rxLength>0 && (readingKey&0x01)){ if(rxLength==5) {modifier = temp;} else if (rxLength==4) {firstKey = temp;} else if (rxLength==3) {secondKey = temp;} else if (rxLength==1) { txMsgBuffer[0]=modifier; txMsgBuffer[1]=firstKey; txMsgBuffer[2]=secondKey; msgCounter=0; UC0IE |= UCA0TXIE; } } if(readingKey & 0b10) rxLength--; } //every ~0.5 seconds #pragma vector=TIMER0_A0_VECTOR __interrupt void Timer_A (void) { msgCounter=0; txMsgBuffer[0]=0x87; txMsgBuffer[1]=0x02; txMsgBuffer[2]=0x8C; UC0IE |= UCA0TXIE; }
The code is, admittedly, a little janky. I don't mind reusing the TXBUFFER for storing responses and I did some work to try to make familiar actions work (like making a caps-lock key combo, etc). Of course, more thorough post to come but, in the meantime, enjoy!
0 notes
Text
Half-Keyboard Schematic + Code
Here's the schematic and code for the half-keyboard test.
You'll have to note a few things:
First, obviously the routing bus is pretty involved. Between the schematic and the board, I have the MUX-es wired up a little different. That's because it's more clear and convenient to put certain wires to the keyboard PCB.
If you happen to want to go through the trouble of doing this yourself, I REALLY recommend creating a map. The way I did mine was to indicate which MUXes were being activated. Here's an example:
For me, I used the red "OFF" column to indicate a connection I purposely made to not work. The black cells are ones that do nothing (apparently).
I found that even my cheapy keyboard had built in functionality to change volume, put the computer to sleep, and open mail applications, even though the actual keyboard had none of these buttons.
Below is the "hello world" code. Quick thing to note-it's easy! That's the beauty of good hardware. It should make coding a breeze.
Enjoy!
//KEYBOARD TESTER - SINGLE KEY //On button press (PIN 12, pulled down) //automatically types "hello world" // //ALEX MCKEE-OTA //Off state = XXX01001 //LETTER DEFINITIONS //letters char a=0b11110111; char b=0b01001100; char c=0b10001110; char d=0b11101110; char e=0b10101110; char f=0b11101100; char g=0b01101100; char h=0b01101111; char i=0b10101101; char j=0b11101111; char k=0b11101101; char l=0b11101010; char m=0b10001111; char n=0b01001111; char o=0b10101010; char p=0b10100010; char q=0b10110111; char r=0b10101100; char s=0b11111111; char t=0b00101100; char u=0b10101111; char v=0b10001100; char w=0b10001100; char x=0b10011111; char y=0b00101111; char z=0b10010111; //numbers char zero=0b11000010; //shift-> ) char one=0b11010111; //shift-> ! char two=0b11011111; //shift-> @ char three=0b11001110; //shift-> # char four=0b11001100; //shift-> $ char five=0b00001100; //shift-> % char six=0b00001111; //shift-> ^ char seven=0b11001111; //shift-> & char eight=0b11001101; //shift-> * char nine=0b11001010; //shift-> ( //math chars from numpad char mult=0b10001011; char divide=0b10000110; char sub=0b01001011; char add=0b10101000; //navigation and manip char ent=0b10000101; char del=0b00000100; char bkspc=0b00100101; char dwn=0b01000100; char rt=0b01000110; char lft=0b01001000; char up=0b01101000; char pgup=0b00001011; char pgdwn=0b11001011; char Home=0b00001000; char End=0b11001000; char tab=0b00110111; //special chars char esc=0b01110111; char menu=0b01001010; char win=0b00100011; //punctuation & brackets char per=0b10001010; //shift-> > char com=0b10001101; //shift-> < char sqlft=0b00100010; //shift-> { char sqrgt=0b00101101; //shift-> } char slash=0b01000010; //shift-> ? char semic=0b11100010; //shift-> : char apost=0b01100010; //shift-> " char spc=0b01100100; char off=0b00001001; //modifiers char caps=0b001111111; char ctrl=0b10000000; char alt=0b01000001; char shift1=0b00100111; char shift2=0b11100111; char L1[16]={caps,h,caps,e,l,l,o,spc,caps,w,caps,o,r,l,d,per} ; void writeLetter(int letter[]); void setup(){ pinMode(0,OUTPUT); pinMode(1,OUTPUT); pinMode(2,OUTPUT); pinMode(3,OUTPUT); pinMode(4,OUTPUT); pinMode(5,OUTPUT); pinMode(6,OUTPUT); pinMode(7,OUTPUT); pinMode(12,INPUT); pinMode(13,OUTPUT); digitalWrite(13, HIGH); writeLetter(off); } void loop(){ if(digitalRead(12)==1){ /*for(i=0;i<sizeof(L1);i++){ writeLetter(L1[i]); writeLetter(off); }*/ writeLetter(h); delay(50); writeLetter(e); delay(50); writeLetter(l); delay(50); writeLetter(l); delay(50); writeLetter(o); delay(50); writeLetter(spc); delay(50); writeLetter(w); delay(50); writeLetter(o); delay(50); writeLetter(r); delay(50); writeLetter(l); delay(50); writeLetter(d); delay(50); writeLetter(ent); delay(50); } writeLetter(off); delay(50); } void writeLetter(unsigned char letter){ for(int counter=0; counter<8;counter++){ if (letter&0x01) digitalWrite(counter,HIGH); else digitalWrite(counter,LOW); letter=letter>>1; } }
0 notes
Text
(Half) Keyboard test
hello world.
That was typed by a program!
Right, right, not that impressive, I know…but that’s because the program you’re thinking of is on the local machine from which I’m typing…but it isn’t!
A nice big post is coming up when this is done but, in the meantime, I’ve got the “back-end” of the system pretty hammered out. It all started when I opened a super cheap (i.e. <$7.00) USB keyboard. It’s dead simple. All you have are 8 pins on a layer of plastic with each of the pins connected to a set of the keys on the keyboard. Next, there are 18 pins connected through another sheet of super thin connectors to the entire set of keys on the keyboard. A sheet of plastic divides the two wired sheets with holes under each button. When you press a button, it bridges the gap between the two, closing a switch.
We want to control all of that digitally though. Do that, and you've got a modular keyboard controller. Here's the high level design:
Fig. 2 - High-level interpretation of the half keyboard controller
We'll go through top to bottom:
Some (any) serial data stream goes into our microcontroller. We can control our system with any two-wire solution. I had only two GPIOs left on my micro, a simple MSP430G2211.
We use some functions inside the micro to determine what the serial data means and that determines our 8-bit routing info.
The 8-bit data that comes out is really just for the sake of routing data from one portion of the keyboard PCB to the other and connecting them as if by a key press. The number of bits was determined in two ways: i) logically and ii) by what parts I had which were 4 x 4051's (8-to-1 analog multi/demultiplexer). I'll get to all of that in more depth in case it isn't clear.
The MUX's job is to choose one of 8 inputs to route to its single output line. The single output goes to the DMUX's single input and goes out to one of 18 outputs, simulating a key press.
The keyboard PCB thinks that there's a key-press and is jolly sending off info to the USB.
Of course I only know what I know from howstuffworks.com and taking a keyboard apart so, after scouring craigslist and ultimately buying from amazon, I had my keyboard! It's a V7 standard USB keyboard that you can find on amazon. In retrospect it wasn't the best choice (see improvements number 3), not because of the electronics, but solely because of the contacts which were unsolderable contacts that work by pressing the leads from the plastic sheets against it. I had to scratch these with a tiny precision screwdriver until the surface was rough and I go to a little bit of metal and soldered some equally sized wires to it.


This of course was not before mapping out all of the keys to each of the leads. First I used a sharpie to map out where each key was, then I dry erase traced the connections to each one. The result is the page below.



I know this is probably out there on the net somewhere but gotta keep the brain sharp!
So what I found was that I needed to connect one of 8 pins to one of 18 pins since the last two on the don't trace to the main blob IC on the PCB.
Next up, I had to figure out which MUX/DMUXs to use. I had a set of digital ones as well as the 4051's from an IC grab bag from electronic goldmine which I definitely recommend, but the digital ones didn't cut it for reasons that will later become clear. I measured the voltage on all of the pins with the keyboard PCB plugged in and they were all at about 5v which puzzled me and I would love to know the answer at some point. (edit: I put a bunch of the terminals to the ol' scope - turns out one of the sides has a slowish signal where it drives the line low. When two sides are connected, the signal is read by the other side to confirm a key-press. That way, a combination of key presses is a combination of signals!) In the meantime, the other part I had (very luckily) were four happy healthy 4051's. They are the perfect all purpose chip. They have inputs ABC to determine routing, function as both multiplexers and demultiplexers, and have 8 I/O's to 1 O/I which is a really good number. Checking the datasheet (here), it's got a great transient response with rise/fall speed at 3.75us/V. For our case, that basically means for a worst case scenario where we need to switch from 0 to 5v instantly, it would only take 18.75us!
Okay, so for the preliminaries, it will take a minimum of 4 x 4051. I determined that by treating this thing like a puzzle. Besides that, we will call each MUX by a number 1, 2, 3, and 4, and their control leads A1, B1, C1 for chip 1 for example and A2, B2, C2 for chip 2. The inputs/outputs we won't worry about too much and should be clear as we explain. Also, keep a tally of how many pins we are using on our micro because we want to be efficient!
We can configure them many ways but the most logical thing is to use MUX 1 for those first 8 pins. It makes no sense to do it any other way. That will take up 3 micro pins to control. Now we have 18 pins left for the other 3 MUXes.
It would be perfect if we had a similar chip that had one line to 32 because then we would only need 5 more bits but, since I didn't, we'll just be a little less efficient. Total, we have 3 x 8 = 24 output pins we can address on the three chips, but that would take 9 control pins: A2, B2, C2, A3, B3, C3, A4, B4, and C4.
So how could we use less pins? Let's look at two DMUXs, chip 2 and 3. We could connect their control lines together (A2-A3, B2-B3, and C2-C3) and then we would use three bits to address 1 of 8 outputs on chip2 and 1 of 8 outputs on chip3. Obviously we don't want to connect the input to both simultaneously so instead we use chip 4 to choose which of the two gets the input while the other is floating (which is fine since each pin is floating when you don't press a key). We need one bit to do that from Chip 4 (A4). But that means we only have 16 of the 18 outputs we need. So we expend one more control line, B4, to control four outputs - one for each of chips 2 and 3 and two more for additional output lines. We could splurge and use the third control line, C4, to get four more outputs, but we've reached the magical number of 18 already so no need!
Altogether, that's 3 bits for the MUX, and 5 bits for the DMUX controls for a total of 8 pins which, if we already had the hardware, would be the least we would need anyway!

I laid out my perf, soldered away, and tested it with an Arduino since it's fast and very controllable--all around great for prototyping. I just wired up to the IC socket where the MSP430 would go and wrote up a program to control everything.

There was only one big issue left: using modifier keys like the shift or alt or control since the re is only one link between keys. The big problem is that I built everything up thinking that it some DC current that triggered the key press when lo and behold, it was an AC signal (hence why the digital MUXes didn't work). Yarg! I'll tell you the treacherous way I took.
Really, it came down to needing more I/O. Eventually in this project, I would need an ADC for touch screening on the final project but, alas, the 2211 doesn't have an ADC onboard. I moved up to the much beefier 2553. It also has a UART module which I'll use later for getting input to this keyboard.
With an extra few I/O and a transistor circuit, I could keep a modifier key "held" in software while simultaneously pressing another key.
Results post to come soon.
Improvements
There are a few words of warning to the reader who wants to duplicate this.
You don't need to leave one pin disconnected! There are combinations along the line that don't have a function so you can just send that combination as an "off keyboard" command.
If you're working on Windows and (I think) Mac, there is an "accessibility" option within the operating system called "Sticky Keys" which holds a modifier key after you first press it, and then, after you push your first key after that, it will apply the modifier to that key. That way you can type "shift," then "k" in order rather than holding shift and you will still get a capital "K." If you have that available, the capacitors and calculations are totally unnecessary. There is just a threshold system level speed between key-presses to worry about that I've found to be around the 10's of ms (10ms won't work but 20ms will be okay). If you keep it at 20ms, you can't type faster than that so you won't have issues!
The keyboard I chose was one of the cheapest. The keyboard contacts were some sort of black contact connections with the keyboard contacts being pressed against them to connect the two. You can't solder these straight away-my attempts ended in a very charred/flux covered board. Experiment with different boards, post them here and if you find a great one with some solder-able contacts, post it! I would love to know a great brand to do the same thing with without breaking the bank.
0 notes
Text
Resistor Dividing with a Sensor

I once lived with a guy named Nemo. He's on to grad school now but late at night, we would always stay up huddled around a blackboard solving problems.
He teaches a class from time to time, now, where he gets to show students the MyDaq from National Instruments and he came to me with a question on using the ADC to measure light hitting an LDR. As usual, we stayed up late into the night, overengineering the most basic circuit--the voltage divider. So here's the problem:
The resistor divider is all well and good for sensors, but how do you choose the second resistor?
Well, we reasoned out, you want the widest range of course! Once you get the idea, it's all just a bunch of simple math.
For your geeky pleasure, here it is reproduced. First, we need data from the sensor. LDR's have high resistance in the dark and low resistance in the light so, first, we'll need to measure at it's lightest--when our bright LED light shines on it:

And at its darkest (i.e. when my finger covers it. Excuse the long exposure hand-jitters):

So our two values are 106.1kOhms in the dark and 2.349kOhms in the light.
Ideally, our equation should depend on those two values to come up with a new resistor value.
Let's do the math. Since we want the voltage to increase when it's light, we want to use the sensor as the upper resistor.
Then, we have two voltage equations:

for the upper voltage when it's light and

for the lower voltage when it's dark. We want the largest difference between the two so we find an equation for their difference:

and then take the derivative since we would like to find the maximum of this difference parameter:

.
From there, it's really just simplification; we give each fraction a common denominator and factor out the constant Vi, then use the quotient rule to find the derivative and set to 0:




After setting to 0, we know that he denominator is useless (unless it approaches infinity which would not be a real answer for us) so we throw it out and just solve for the numerator:

and then we simplify...

...and simplify...

...and finally...

Haha! Now things look better. Solving for R:

Let's check for our case.

The MyDaq has a power line at 5v so we'll assume we're using that to find our max and min voltage which should be pretty reasonably close to 5v and 0v:


Pretty good! And so once again, calculus saves the day! The next time you need a resistor divider for a resistive sensor (force sensitive resistor, thermistor, etc), just get the extreme resistance values and plug them in and you'll find the optimal second resistor!
By the way, I tried the same calculation with the upper and lower resistances swapped and the same answer arises so, if your want a "dark sensor," you can just switch the top and bottom resistors!
Hope this helps!
0 notes