Hello again! In this post I will discuss my progress regarding the key-detection feature of the Hermes 3000 Upcycled Typewriter project. If you missed my first two posts you can view them here:
In my last post I showed how I attached loops of copper wire to the typewriter to act as part of a "normally-closed" switch (with the type-bars acting as the other part). I have now attached wires to these loops so they can be connected to the shift registers. Check out this picture, it looks like my typewriter has some funky hair!
You may notice a few things in the image above. First, the wires are different colors. There are 44 wires total so I used a different color for each group of 11 wires. Can you imagine 44+ wires of the same color running through the typewriter? It would be very difficult to trace out a specific wire when troubleshooting. Next, you may notice the wires have pins on them. I crimped pins on the ends of the wires so I could plug them into a breadboard easily. It was a tedious but necessary task. Lastly, you may not be able to see from the picture but those are silicone wires. Silicone wires are more flexible and flexibility is important when working with limited space (like inside a typewriter).
After I had the wires attached, I decided to work on the code for the MCU. In my previous post I explored programming the MCU to read data from one shift register. Now I need to expand that to 6 shift registers - which is a fairly straightforward task. Of course there is a difference between simply reading inputs and actually interpreting those inputs...
I realized that sometimes, when typing, I will accidentally hit multiple keys. For example, if I was typing an 'a', I might also slightly tap the 's' key. It won't be enough to lift the 's' type-bar up to the paper, but it's enough to lift it off the loop of copper wire briefly. The Edison needs to know how to distinguish between the real input (the 'a' in this case) and the false input (the 's' in this case). I'm thinking the best way to do this is based on timing. The Edison will see the real input being "on" for a longer time than the false input, so maybe I can use this to my advantage...
I have come up with the following algorithm. It is based on timing and involves two important values that I am calling framerate and checkrate. The framerate value is the number of milliseconds that elapse between reading the data from the shift registers. Every time the MCU reads the data from the shift registers, it stores this in an array which I am calling a "frame" (the idea is similar to the way a video camera rapidly takes pictures - also called frames). The checkrate value is the number of frames the MCU will read before trying to decode the data. So here's how it all works: every framerate milliseconds, the MCU shifts the data in from all 6 registers, stores it in a frame, and puts that frame into an array of frames. After it has read checkrate frames, it will move on to decode them. The way the frames are interpreted are by a simple AND procedure. If a key was held down during all the frames, then the corresponding bit in each frame will be a 1. If a key was accidentally pressed, then its corresponding bit should only be 1 in some frames, but 0 in others. So if I perform a bitwise AND on each frame in the array, I will be left with a "master frame" which should only have one bit that is 1. This bit would represent the key being pressed. I just need to experiment with the values of framerate and checkrate to determine the optimal values.
You can see this code below. Note that I have not implemented the "decode" function yet - but that should be simple.
#include "mcu_api.h" #include "mcu_errno.h" //FRAMERATE: 20ms //CHECKRATE: load 5 frames before checking inputs (total 100ms) enum {CHECKRATE = 5, FRAMERATE = 20}; //Data pins are the serial data coming from //shift registers. In order, IO2 = IO7. int datapins[6] = {128, 12, 129, 13, 182, 48}; //Control signals for shift registers. int SH_LD = 49; //IO8 int CLK_INH = 183; //IO9 int CLK = 41; //IO10 //For receiving a "command" from the host system. //Right now there are no commands unsigned char cmd[255]; unsigned char result[1] = ""; char frames[CHECKRATE][6]; int i, j; //loop variables /* * See datasheet for proper operation of shift * register (74HC165). */ void shift_in(int frame){ //Load into register gpio_write(SH_LD, 0); gpio_write(SH_LD, 1); //Turn off clock inhibit gpio_write(CLK_INH, 0); //Get data from register for(i = 0; i < 8; i++){ for(j = 0; j < 6; j++){ //Shift data in frames[frame][j] <<= 1; frames[frame][j] |= gpio_read(datapins[j]); //Cycle the clock gpio_write(CLK, 1); gpio_write(CLK, 0); } } //Turn clock inhibit back on gpio_write(CLK_INH, 1); } unsigned char decode(char frame[]){ /* * Still need to implement this. * Basically, maps the data to a specific character */ } void mcu_main() { for (i = 0; i < 6; i++){ gpio_setup(datapins[i], 0); } gpio_setup(SH_LD, 1); gpio_setup(CLK_INH, 1); gpio_setup(CLK, 1); gpio_write(CLK_INH, 1); gpio_write(CLK, 1); gpio_write(SH_LD, 0); unsigned long time = time_ms(); int frame_count = 0; char master_frame[6]; while (1) { //Every FRAMERATE ms we want to read a new frame //until we have read CHECKRATE frames. Then we will //decode the input and send it to the CPU. if(frame_count == CHECKRATE){ frame_count = 0; for(i = 0; i < CHECKRATE; i++){ for(j = 0; j < 6; j++){ master_frame[j] = master_frame[j] & frames[i][j]; } } } if(time_ms() >= time + FRAMERATE){ //enough time has passed, check frame time = time_ms(); if(frame_count < CHECKRATE){ //not enough frames checked yet, check frame shift_in(frame_count); frame_count++; } else { } } } }
Note that I haven't tested this code yet - so if you see any glaring problems please let me know! While I'm talking about testing, I should mention another aspect of this project. You see, when I finally test this system and I don't get the results I expect, how will I know where the problem is? It could be that the code is wrong or maybe the typewriter isn't wired correctly. So I decided to design a testing board. Basically it's a bunch of LEDs - 44 to be exact - which will turn on when a key is pressed. This way I can see what the Edison would "see". You can see this testing board in use below.
The board is pretty simple. Basically, the wire that would go to the Edison now goes to the base of an NPN transistor. The transistor controls an LED. See how the LEDs are color-coordinated with wires? Pretty smart, eh? And they are in order - the 3rd blue LED corresponds to the 3rd blue wire on the typewriter. The whole thing is powered by a 12V lead-acid battery connected to a 5V linear voltage regulator. So now when I press a key, a light will turn on! This board took a while to set up but it has already revealed some very useful information. First, some of the type-bars weren't resting on the copper loops very well. I have adjusted some of them to fix the problem but some others will require more work. Second, I realized that while I'm typing, lots of the LEDs flicker! I believe that this is caused by the constant vibration that the typewriter feels while someone is typing. This vibration is enough to cause the type-bars to break the connection with the wires momentarily. The Edison will read this as me hitting many keys at once, but I believe that my code will be able to distinguish between the real key press and the vibrations causing momentary "glitches".
So that's where I am now! Here's what I need to do next:
- Fix the copper loops to ensure good connectivity
- Finish the code and test it!
- Wire up the keys that don't use shift registers (like "shift")
Stay tuned next week for the continuation of this project! Let me know if you have any questions, comments, concerns, or criticisms.
P.S.: I would like to add a special thanks to a youtuber - TypewriterJustice. Previously, I had removed the carriage of the typewriter (the part that holds the paper and moves left/right as you type). Well it turns out that only typewriter experts should do this! I tried so many times but could not re-install the carriage . So in my desperation, I sent a message to TypewriterJustice asking him if he had any tips. He responded to me very quickly and with his advice (and a little luck), I managed to get it back on! Thanks TypewriterJustice!