-
Finishing Touches
10/06/2023 at 00:11 • 0 commentsWhen I did the wiring, I chose to use jumper wires to connect to the proMico rather than soldering directly to the header. I was glad that I did because the USB connector on the pro Micro broke and I had to replace the board. At any rate the jumper wires extended below the bottom of the case, so I added some rubber feet which not only fixed that issue, it made the box more stable.
I cleaned up the MacroPad sketch and created a Configure_MicroPad sketch to help calibrate the linear hall effect sensors (see Calibration log). I also added auto repeat functionality.
-
Calibration
10/05/2023 at 15:54 • 0 commentsIn order to get accurate tile readings, it's important to calibrate the linear hall effect sensors. I found with my TMD-3 project that for the best results each sensor should have its own calibration values for each of the possible tile variations. With 16 sensors and 10 possible tile variants this can be a pretty tedious process. So to make things easier I create a calibration sketch. NOTE that you only have to do this calibration once.
To start I create a special set of calibration tiles.
Each tile contains two magnet holders set to the number that appears in the label.
The calibration program runs from the Arduino IDE and outputs to the Serial monitor. With NO tiles in the MacroPad when the program first starts up you see the following:
The midValues array printed represents the "at rest" or no magnet present readings from the hall effect sensors. Notice that the values do not vary much between sensors. This table should be copied into the MacroPad sketch replacing the existing table with freshly calibrated values.
Following the prompt, when you put the "zero" tile into each of the eight slots and press the corresponding buttons, you should see something like:
For each button pressed the two sensors for that button are read and the sensor numbers and values are emitted. When all eight slots have been calibrated the next tile number is asked for.
When all ten tiles have been calibrated the calValues table is emitted.
Simply cut the table text from the Serial window and paste it into the MacroPad sketch replacing the existing table.
The MacroPad should now be setup to accurately read tiles and correctly map them to the defines macros.
I have posted the Calibrate_MacroPad.ino sketch to GitHub.
-
Even More Magical Tiles
10/02/2023 at 21:59 • 0 commentsWhen doing the firmware I ended up deciding that the encoded tile number would be used as an index into the macros table of macro strings. But because each of the two digits in a tile could only go up to 8, it meant that the macro table ended up having gaps marked "Skip for now.".
// Define the macros here. String macros[] = { "PRESS,KEY_LEFT_CTRL,a,RELEASE_ALL", // select "PRESS,KEY_LEFT_CTRL,c,RELEASE_ALL", // copy "PRESS,KEY_LEFT_CTRL,x,RELEASE_ALL", // cut "PRESS,KEY_LEFT_CTRL,v,RELEASE_ALL", // paste "KEY_UP_ARROW", // up "KEY_LEFT_ARROW", // left "KEY_RIGHT_ARROW", // right "KEY_DOWN_ARROW", // down "", // Skip for now. "", // Skip for now. "MEDIA_VOLUME_UP", // louder "MEDIA_VOLUME_DOWN", // softer "MEDIA_MUTE", // mute "MEDIA_PLAY_PAUSE", // pause/play "" };
No big deal but also easily fixed. I decide to add another magnet holder to the mix which would allow two more numbers per tile side.
When looking at the readings from one side of an existing tile:
136, 72, 54, 37, -123, -87, -56, -37
you can see that there is a pretty big "gap" between the first two numbers and the fifth and six numbers. This represents the difference in distance from the sensor between 0 mm and 1 mm. So I added another magnet holder with the distance set to .6 mm. Here is my new set of holders:
With ten digits per magnet, I can now have up to 100 macros stored, and no gaps in the macros table.
-
Firmware
10/02/2023 at 17:39 • 0 commentsI have posted an initial version of the Arduino based firmware to GitHub. It handles the reading of the tiles, and assigns the macros to specific keys. I'm using the Arduino HID-Project library to send the keys via USB to the PC. This is working well.
When I started looking at similar MacroPad projects, at least the ones that were not based on QMK or Kaleidoscope, I found that they were all virtually mapping keys directly to some code. For instance:
switch(button){ case '1': //Return Keyboard.press(KEY_RETURN); Keyboard.releaseAll(); break; case '2': //Escape Keyboard.press(KEY_ESC); Keyboard.releaseAll(); break; default: break; };
For every macro added or changed then the code would get modified. Not a big deal but it could be easier, especially maybe for non-programmers. In my implementation the user can define the macros as comma delimited strings. Here are my macro definitions so far.
It starts with the definition of keyboard and media codes.
/***** * Standard Key Codes. ****** KEY_RESERVED KEY_ENTER KEY_PAGE_DOWN KEY_ERROR_ROLLOVER KEY_RETURN KEY_RIGHT_ARROW KEY_POST_FAIL KEY_ESC KEY_LEFT_ARROW KEY_ERROR_UNDEFINED KEY_BACKSPACE KEY_DOWN_ARROW KEY_A KEY_TAB KEY_UP_ARROW KEY_B KEY_SPACE KEY_RIGHT KEY_C KEY_MINUS KEY_LEFT KEY_D KEY_EQUAL KEY_DOWN KEY_E KEY_LEFT_BRACE KEY_UP KEY_F KEY_RIGHT_BRACE KEY_NUM_LOCK KEY_G KEY_BACKSLASH KEYPAD_DIVIDE KEY_H KEY_NON_US_NUM KEYPAD_MULTIPLY KEY_I KEY_SEMICOLON KEYPAD_SUBTRACT KEY_J KEY_QUOTE KEYPAD_ADD KEY_K KEY_TILDE KEYPAD_ENTER KEY_L KEY_COMMA KEYPAD_1 KEY_M KEY_PERIOD KEYPAD_2 KEY_N KEY_SLASH KEYPAD_3 KEY_O KEY_CAPS_LOCK KEYPAD_4 KEY_P KEY_F1 KEYPAD_5 KEY_Q KEY_F2 KEYPAD_6 KEY_R KEY_F3 KEYPAD_7 KEY_S KEY_F4 KEYPAD_8 KEY_T KEY_F5 KEYPAD_9 KEY_U KEY_F6 KEYPAD_0 KEY_V KEY_F7 KEYPAD_DOT KEY_W KEY_F8 KEY_NON_US KEY_X KEY_F9 KEY_APPLICATION KEY_Y KEY_F10 KEY_MENU KEY_Z KEY_F11 KEY_1 KEY_F12 KEY_2 KEY_PRINT KEY_3 KEY_PRINTSCREEN KEY_4 KEY_SCROLL_LOCK KEY_5 KEY_PAUSE KEY_6 KEY_INSERT KEY_7 KEY_HOME KEY_8 KEY_PAGE_UP KEY_9 KEY_DELETE KEY_0 KEY_END You can add any of these codes to the keyboard lists below for use in macro definitions. In the keyboardKeyNames table add the code as a string delimited by "". In the keyboardKeyCodes table ad the code as is without quotes. Don't forget the commas at the end of each entry. It is IMPORTANT to add the name and code in the same position in the list. *****/ String keyboardKeyNames[] = { "KEY_LEFT_CTRL", "KEY_UP_ARROW", "KEY_DOWN_ARROW", "KEY_LEFT_ARROW", "KEY_RIGHT_ARROW" }; KeyboardKeycode keyboardKeyCodes[] { KEY_LEFT_CTRL, KEY_UP_ARROW, KEY_DOWN_ARROW, KEY_LEFT_ARROW, KEY_RIGHT_ARROW }; /****** * Media Key Codes. ******* MEDIA_RECORD MEDIA_VOLUME_MUTE MEDIA_FAST_FORWARD MEDIA_VOL_MUTE MEDIA_REWIND MEDIA_VOLUME_UP MEDIA_NEXT MEDIA_VOL_UP MEDIA_PREVIOUS MEDIA_VOLUME_DOWN MEDIA_PREV MEDIA_VOL_DOWN MEDIA_STOP MEDIA_PLAY_PAUSE MEDIA_PAUSE You can add any of these codes to the media lists below for use in macro definitions. In the consumerKeyNames table add the code as a string delimited by "". In the consumerKeyCodes table ad the code as is without quotes. Don't forget the commas at the end of each entry. It is IMPORTANT to add the name and code in the same position in the list. *****/ String consumerKeyNames[] = { "MEDIA_PLAY_PAUSE", "MEDIA_VOL_MUTE", "MEDIA_VOLUME_UP", "MEDIA_VOLUME_DOWN" }; ConsumerKeycode consumerKeyCodes[]{ MEDIA_PLAY_PAUSE, MEDIA_VOL_MUTE, MEDIA_VOLUME_UP, MEDIA_VOLUME_DOWN }; /***** * Define the macros here. ***** String macros[] = { "PRESS,KEY_LEFT_CTRL,a,RELEASE_ALL", // select "PRESS,KEY_LEFT_CTRL,c,RELEASE_ALL", // copy "PRESS,KEY_LEFT_CTRL,x,RELEASE_ALL", // cut "PRESS,KEY_LEFT_CTRL,v,RELEASE_ALL", // paste "KEY_UP_ARROW", // up "KEY_LEFT_ARROW", // left "KEY_RIGHT_ARROW", // right "KEY_DOWN_ARROW", // down "", // Skip for now. "", // Skip for now. "MEDIA_VOLUME_DOWN", // louder "MEDIA_VOLUME_UP", // softer "MEDIA_VOL_MUTE", // mute "MEDIA_PLAY_PAUSE", // pause/play "" };
When a key on the pad is pressed, it's corresponding tile is read to get the tile number. This number is used as an index into the macros table to get the appropriate macro string which is parsed to determine the keys to send to the PC. The macro string is split into "tokens" at the commas and the tokens are processed from left to right with the following rules:
- If the token is "PRESS", a flag is set to to indicate that all subsequent characters (c) will be sent with the Keyboard.press(c) function. If the press token is not set (default at start of parsing), characters will be sent with Keyboard.write(c).
- If the token starts with "KEY_", then the token is used to lookup a keycode in the keyboardKeyCodes table. That keycode is sent to the PC via the Keyboard press or write functions depending on the press flag.
- If the token starts with "MEDIA_", then the token is used to lookup a keycode in the consumerKeyCodes table. That keycode is sent to the PC via the Consumer press or write functions depending on the press flag.
- If the token is "RELEASE", then a release flag is set. The next character processed (c) will be sent to the PC with a Keyboard.release(c) function and the release flag will be cleared.
- If the token is "RELEASE_ALL", then the Keyboard.releaseAll() function will be executed and the release flag cleared.
- If the token is a hex digit of the form "0xnn" exactly where nn is a valid hex number, then the value based on that number will be sent to the PC via the Keyboard press or write depending on the press flag.
- If the token is not recognized as one of the "key words" above, then it’s assumed to be an ASCII character or a string. If the token is a single character it will be sent to the PC via the Keyboard press or write depending on the press flag. If it's a string (s) it will be sent to the PC with a Keyboard.print(s) call. (NOTE there is no corresponding Keyboard.println(s) call because the user can simply add a \n to the end of the string if that is what they want.)
So someone could define the macros within this code without knowing any programming. Mind you to get the firmware onto the pro Micro they will still have to, at a minimum, learn the Arduino IDE.
-
Wiring
09/28/2023 at 17:23 • 0 commentsThe first thing I did was to trim the leads on the SS49E sensors and spread them out a bit for easier access.
Then I wired the power connections to all the sensors and the buttons. Obviously a lot of care was taken not to introduce shorts, especially with the tiny hall effect sensors. The perfectionist in me cringes a bit looking at this, but I am determined to keep the BOM down so point-to-point wiring it is.
Next I wired the outputs of the 16 sensors and power to the multiplexer (left below). Finally I connected the pro Micro (right below) to the buttons, multiplexer, and project power.
Here are all the connections.
Arduino pro Micro CD4067BE Multiplexer Wire Color Other VCC +5V Dark Orange Also powers the sensors. GND GND Black Project Ground A0 Common Input (Read the channel selected by S0-S3) White I/O Pin 2-9 Light Orange Button 1-8 I/O Pin 15 S0 (These four outputs select which of the 16 analog channels to read.) Yellow I/O Pin 14 S1 Yellow I/O Pin 16 S2 Yellow I/O Pin 10 S3 Yellow I0 - I15 Green Sensors 1-16 GND E (This is an inverted chip select pin.) Black I wrote a quick and dirty test program and discovered a couple of bad solder joints, but everything seems to be working as expected now. Software time.
-
Building Up The Base
09/27/2023 at 21:46 • 0 commentsThe core of this MacroPad will be built around this 3D printed base.
The center panel has holes to mount the eight Fubata MD-4PCS switches. To the left and right are eight tile slots. You can see at the bottom of each slot two indentations to hold SS49E linear hall effect sensors with holes to pass through the sensor's leads.
Adding The Sensors
- Start by bending the leads on one of the SS49E linear sensors at about 1 mm from the base towards the flat side of the sensor to about 80 degrees.
- Insert the sensor into one of the slot indentations by sliding the leads through the slot's hole and pressing it in place. You should be able to feel it quietly clicking into place.
- Add the second sensor to the slot in the same way except that it's oriented 180 degrees from the first. Get one of the slot bottom cover pieces. Notice that there are two raised bumps.
- Glue the bottom cover into place making sure that the bumps are facing down. You should apply the glue around the SS49Es without getting any glue on the sensors. (Note: I got burned once when the glue I was using was actually conductive.)
Repeat for the other slots and sensors.
Adding The Buttons
Here is a look at my Fubata MD-4PCS switches which I'm using because I still have a bunch from some of my retro computer projects. They are nice and clicky as retro keys should be. Snap in the buttons making sure that the two small studs (one of which is circled in red below) align with the holes in the base.
I love the light lavender keycaps which as I have mentioned inspired the overall aesthetic for this build.
On the backside I now have wiring access to the sensors and switches. In addition I designed a frame to hold the pro Micro and the CD4067BE 16-Channel Analog Multiplexer.
Here is another look. The pro Micro and multiplexer will be attached with two sided tape.
Ready to wire.
-
Talking To Tiles
09/27/2023 at 01:41 • 0 commentsWith tile construction set, we now have to calibrate the reading of tiles and establish a "protocol" for mapping tiles to macros.
I'm using the same SS49E Linear Hall Effect Sensor that I did with the TMD-3 project so I know that the Analog To Digital (ADC) default reading from the sensor (with no magnetic field nearby) is the middle of the range. Furthermore a magnet with one polarity will move the reading to higher values while the opposite polarity moves it to a lower values. So I printed a tile slot and populated it with a single SS49E sensor. The sensor is pressed into a precisely sized and positioned indentation at the bottom of the slot with the leads extending below.
I wired the sensor to my pro Micro.
Pro Micro SS49E VCC +5V GND GND A0 OUT I wrote a simple sketch to read the sensor.
/* MacroPad Sensor Calibration. */ int sensorPin = A0; // Sensor input pin. int sensorValue = 0; // Sensor value. int sensorMid = 0; void setup() { Serial.begin(115200); Serial.println("MacroPad Sensor Test!"); sensorMid = analogRead(sensorPin); } void loop() { // Read the value from the sensor. sensorValue = analogRead(sensorPin) - sensorMid; if (abs(sensorValue) > 20) { // Magnet detected. Serial.println(sensorValue); } delay(1000); }
Using this code I established the following table.
Assigned Number 0 1 2 3 4 5 6 7 Magnet Distance 0 mm 1 mm 2 mm 3 mm 0 mm 1 mm 2 mm 3 mm Polarity N N N N S S S S Sensor Reading 180 104 79 53 -164 -114 -81 -49 The important thing here is the first and last rows. If the value read from the sensor is "close" (there will always be a little variance between different sensors and tiles) to one of the values in the last row, then that position will be assigned to the number in the first row from the same column. So for instance if the sensor value read is say 50, then the value for that position will be assigned a value of 3.
Remember that each slot has two sensors. When you drop in a tile, the assigned value from the left position will be combined with the assigned value from the right position to form a two digit "key" that will map to a specific macro. Astute readers will realize that each tile will thus have a valid two digit octal number key associated with it. Of course care must be taken to make sure that all tiles have a unique key.
So here are my first 12 tiles ready to have the top labels attached.
12 down 52 to go.
-
Making Magic Tiles
09/26/2023 at 15:47 • 0 commentsOK I know the tiles are not magic, they are magnetic, but despite my advanced years I still find magnetism well, magical.
A tile starts with a base. The base has two square "wells" to hold the magnets. I learned from TMD-3 that with one magnet and a single linear hall effect sensor, given the thickness of a tile as a constraint and the sensitivity of the sensor, you can reliably differentiate between about 8 different tiles. So with two magnets and two sensors I can have 64 unique tiles. You can see the dark violet base below.
You can also see in this picture four lavender magnet holders. When trying to measure the strength of the magnetic fields consistently between different tiles it's important to set the magnets in the exact same position each time. Each of the four magnet holders positions the magnet at a different distance from the base (where the sensor will be), from 0 mm to 3 mm. Of course we know that the magnetic field strength (magically :-) decreases as distance increases. The linear hall effect sensors are responsive enough to detect these small differences.
The voids in the magnet holders are sized to snugly hold two 6 mm diameter x 1.6 mm high neodymium magnetic disks. There are also some wedges to make sure that the magnets inserted from the side are precisely centered over the sensor. Here is a better look at the holders. The numbers help me keep them straight.
But Mike that's good for only four different tiles. Right, but it turns out that the hall effect sensors I am using can detect the polarity of the magnetic field in addition to its strength. So you can double the number of different tiles recognized to eight by just reversing the polarity of the magnets and using the same four holders.
To make a tile simply drop two "loaded" magnet holders into the well and attach a labelled "top" to the tile with a few drops of glue. Of course you will have to keep track of the "values" associated with the magnets used and map them to the specific macro that the label of that tile indicates. More on this later.