Scientific calculator hack - CHack ================================== ## Introduction Scientific calculators are the best -when we know how to use them-, but not all are the same. I've owned one for years, since I started college, and at the time I bought the one that fit my needs, the **Casio fx-82ES**. While going further with my career I started to use complex numbers (among other things), and it's a heavy math to do on a non-complex calculator, and the **Casio fx-991es** did had it. Buy a new calculator? No f****** way, it's a lot of money, and a new calculator for just a few more functions. Luckily my good friend Google came up. My calculator could be transformed into a **Casio fx-991es** with just pressing some keys.. a lot of them. However, the problem is that every time it's rebooted/turned off, it goes back to how it was. So every time I needed to use complex I would have to enter the combination. I opened my calculator up and started to see how the keypad works; it's a matrix. simple plain keypad matrix. So, all I had to do was: 1. Mapping of the keys. Done. [Spreadsheet](https://bitbucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/resources/Keys.ods). 2. Find an ic that acted as an electronic key. Done: [CD4066](http://www.nxp.com/documents/data_sheet/HEF4066B.pdf). 3. Write some Arduino code and test it. [It worked](https://youtu.be/F-8bkEwmeZU). 4. Optimize the code. 5. Shrink everything and make a PCB. ### Project probes Here is a photo of the prototype: ![Prototype](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/resources/prototype.jpg "Prototype version") And here a link to [YouTube](https://youtu.be/c7waHOuBSYY) with it working :-) ## Project design The project is to make a board capable of doing the dummy work of pressing the keys for me every time I need to use some of the blocked functions. This means that the board needed to be: * Small so I could carry it with me * Low power so I don't have to change it's battery more often than the calculator itself * Easy to use (one button, maybe two) The board requirements where the most important part, but code is also important. I had a working code from the test mentioned above, but it was ugly, bad coded and so on. I wanted something a little more elegant. While doing this project I read that Hackaday had a [contest](https://hackaday.io/contest/18215-the-1kb-challenge) where the only requirement was to put all the code in 1kB. And there another thing to watch out in the code :-). ## Designing the PCB The PCB needed to have the minimum necessary to fit the above mentioned requirement, so, only just the absolutely necessary components were included: * ATMega328 * CD4066 (x4) * Battery * Button * Led, no project is worth the name *project* without a led (although it will be off all the time to save battery). One very important thing is that the battery needs to last at least the same as the calculator one, and be a coin type battery. If you open the schematics you'll notice there's only the necessary stuff for it to just work. A reset and a button, the cd4066's, decoupling capacitors, and not much more. Some pictures of the finished board: ![Top view](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/resources/board.png "Top view") 3D view of the front of the board ![3D view](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/board/renders/3d_render_a.png "3D render") On a side: ![3D view](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/board/renders/3d_render_b.png "3D render") The back: ![3D view](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/board/renders/3d_render_c.png "3D render") At the moment I already sent the gerbers to Seeedstudio to be made, and they're done. In fact, the boards are in Argentina, but customs has them retained for God knows how long :-| ## Programming So, here I had to do a couple of considerations: 1. It has as little as possible of power while running, much less while sleeping. That was accomplished using the internal oscillator at 8MHz (next I'll speed it down to 1MHz) and almost every module off. The [LowPower](https://github.com/LowPowerLab/LowPower) library provided such functionality. For timming I used the watchdog interrupt, avoiding to use the &mu;C to count and waste energy. 2. All the code must fit into 1kB of program data, so it could participate on the [Hackaday 1kB contest](https://hackaday.io/contest/18215-the-1kb-challenge), and the hunger games began... ### Low Power The first step to reduce the power consumption is to speed it down, and my choice was 8MHz. To speed down the ATmega328 to 8MHz I followed the steps described in the [Arduino To Breadboard](https://www.arduino.cc/en/Tutorial/ArduinoToBreadboard) page. Just by speeding it down and using 3.3v the consumption drops down quite a bit (forgot to write down the figure). But that wasn't enough, it was in the order of the milliamp, it would drain the battery in a few hours. Then I started to turn off modules, almost all of them (ADC, TWI, SPI, etc), and make the &mu;C to do what it had to do and then put it to sleep as soon as possible. In sleep mode, with all turned off the consumption is near 1uA :-), more than perfect. Years on the same battery. ### Shrinking the code To reduce the program size, the first thing was to ditch the comfort Arduino environment and all of it's nice libraries, and jump directly to AVR programming. Luckily the [Arduino-CMake](https://github.com/queezythegreat/arduino-cmake) module has a [command to generate just that](https://github.com/queezythegreat/arduino-cmake#pure-avr-development). After getting into that, and managing to blink a led without the Arduino libraries (not hard at all), I had a "working" directory. Because of the way the circuit is connected, in order to make it "press a button" the &mu;C had to turn on two outputs (_K_ and _KI_), wait a certain amount of time (depends on where the key is pressed), and the number of repetitions we would end up with 6 variables, 1 byte each member: ``` c++ struct Command { uint8_t repeat; uint8_t wait_time; volatile uint8_t* row_port; uint8_t row_pin; volatile uint8_t* col_port; uint8_t col_pin; } ``` I would make an array of __Command__ that contains all the keys to be pressed in the needed sequence. The number of keys to be pressed are 86. If you made the numbers, that's about 516 bytes of program data, a lot! And the other 512 bytes for the contest are a little bit low to write code in C for this &mu;C. I needed to work something out. Then, analyzing the data in the above struct I saw that, except for the ports (by now), all of them are lower than 15 = 0x0F. I could do some trick and reduce the size of the array by merging some members. Let's see: * The time is set by [avr-libc definitions on the watchdog](http://www.nongnu.org/avr-libc/user-manual/group__avr__watchdog.html): - #define WDTO_15MS 0 - #define WDTO_30MS 1 - #define WDTO_60MS 2 - #define WDTO_120MS 3 - #define WDTO_250MS 4 - #define WDTO_500MS 5 - #define WDTO_1S 6 - #define WDTO_2S 7 - #define WDTO_4S 8 - #define WDTO_8S 9 * The repetitions are known, and none of them is bigger than 10 = 0x0A * The pin members save the bit to be set in the port, and the range is 0:7, also lower than 15. The first thing I did was to merge the pin members into one. The result would be: 0b xxxx xxxx row col pin K Ki Then, to fetch the bits we make one reading. and then shift and anding with the corresponding masks: ``` c++ //Get repeat number and time uint8_t r_t = pgm_read_byte(&comandos[i].time_repeat); uint8_t time = ((0xf0 & r_t) >> 4) ; uint8_t counter = (0x0f & r_t); ``` With this trick we save about 80 bytes of program data (need to discount the code used to read the values). Then did the same with both __delay__ and __repeat__: 0b xxxx xxxx time repeat That's about 80 bytes more to data. Now the code size is 996 bytes, almost in the edge xD. The struct now: ``` c++ struct Command { uint8_t time_repeat; ///< The number of times the key has to be pressed, and the time stays on volatile uint8_t* row_port;///< Pointer to the pin port of the column. volatile uint8_t* col_port;///< Pointer to the pin port of the row. uint8_t rc_pin; ///< Row pin on upper nibble, col pin on lower one }; ``` But when I started to tune the time and the sequence I realized I needed some more instructions, and more _Commands_ in the array, shutting the code size way to high. The thing was that I had already made all kind of data size optimizations, on the code itself and with compiler parameters. The ports! Could I merge both ports into one? I started opening files and files of the *avr-libc* code to find the PORT definitions. Found them in file [iom328p.h](http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/avr/iom328p.h?root=avr-libc&view=markup) (or [Github mirror](https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/include/avr/iom328p.h)): * #define PORTB _SFR_IO8(0x05) * #define PORTC _SFR_IO8(0x08) * #define PORTD _SFR_IO8(0x0B) That was great, they fit into one word. So: 0b xxxx xxxx row col port To fetch and use them: ``` c++ //Get data uint8_t _port = pgm_read_byte(&comandos[i].ports); //Mask out and shift decltype(PORTB) row_port = _SFR_IO8((0xf0 & _port) >> 4); decltype(PORTB) col_port = _SFR_IO8(0x0f & _port); //Toggle bit "row_bit" row_port ^= (1 << row_bit); ``` The __Command__ struct now: ``` c++ struct Command { uint8_t time_repeat; ///< Repeat number lower nibble, time in upper one uint8_t ports; ///< Contains both ports uint8_t rc_pin; ///< Row pin on upper nibble, col pin on lower one }; ``` After doing that, and tuning the code, the resulting *final* code size is 884 bytes. The timing of the keys is critical, since I'm not able yet to get any feedback (I haven't tried to hard neither) from the calculator, so I need to know that every "key press" is actually read by the calc, to do that I tried several delays, and the values is around _250mS ± 50mS_. But when the _(_ are being pressed the calculator starts to reach it's characters memory limit (I think), and that makes it's response time to rise up to a couple of seconds. ## Calculator cables In order to use this hack it's mandatory to solder cables to the calculator keys, and put them outside it with a connector. ### Soldering the tracks on the calculator The columns and rows names _k*_ and _ki*_ aren't just because, but Those are the names labeled in the calculator board, so I kept them to make things easier. For each of them one must solder a cable, like the picture: ![Cables and tracks](https://bytebucket.org/samkpo/calculadora/raw/07ed4242aed1a9545022975f0a8905a4c39ad78a/resources/solder1_r.jpg "Soldering the cables") And then, after opening a hole on the calc case, pass the cables (flat cable was my choice) and solder them to a connector. Care must be taken with the names, or you'll have to redo all the _config\_key.sh_ file to match the code and the atmega board. ![Connector on the back](https://bytebucket.org/samkpo/calculadora/raw/07ed4242aed1a9545022975f0a8905a4c39ad78a/resources/solder3_r.jpg "Connector on the back") ### Combinations Back into the problem of the power saving, I made a simple function that will put into power saving the &mu;C and it doesn't have a great impact into the code size. Every CD4066 has 4 switches: ![CD4066](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/resources/cd4066.png "CD4066") In the project pins **1,4,8,11** all go connected together, while the other four, their complements, go to the calculator. That schema is used for each of the rows and columns. (Yes, it might have been better if I connected pins **2,3,9,10**, didn't tried it). So, when I want to connect row _k0_ to column _ki2_, I turn on the switches that are connected to them, and that will connect them together. Each of the columns and row have an ATMega pin assigned (look at the [schematic](https://bytebucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/resources/schematic.png)). The map is kinda like this: For the k: |Nº | PORT | Pin | |:-:|:----:|:---:| |k1 | PORTB | 3 | |k2 | PORTC | 5 | |k3 | PORTC | 4 | |k4 | PORTC | 3 | |k5 | PORTC | 2 | |k6 | PORTC | 1 | |k7 | PORTC | 0 | And the ki: |Nº | PORT | Pin | |:-:|:----:|:---:| |ki1 | PORTB | 0 | |ki2 | PORTB | 1 | |ki3 | PORTB | 2 | |ki4 | PORTB | 6 | |ki5 | PORTD | 4 | |ki6 | PORTD | 5 | |ki7 | PORTD | 6 | |ki8 | PORTD | 7 | With that information I created a [file](https://bitbucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/code/pin_template.h) that contains the key<->col/row mapping in order, with the time they need to be pressed and the number of times to do it. It's a template that is processed by a [bash script](https://bitbucket.org/samkpo/calculadora/raw/500b7390a1f80da699dd8a4772f1a6c93b3b8083/code/config_key.sh). The resulting file is used in the _main.cpp_ file. ### Calculator buttons Map of the keys (k1 and ki1 are the labels on the calculator PCB): |rows|KI1|KI2|KI3|KI4 |KI5 |KI6 |KI7 |KI8 | |:--:|:-:|:-:|:-:|:---:|:-----:|:------:|:----:|:----:| |K1 |1 |4 |7 |RCL |(-) |O/O |Abs |Shift | |K2 |2 |5 |8 |ENG |º “ “” |sqrt(O) |O³ |Alpha | |K3 |3 |6 |9 |( |hyp |O² |< |˄ | |K4 |+ |x |DEL|) |sin( |O^O |˅ |> | |K5 |= |Ans|x10|. |0 | | | | |K6 | | | |M+ |tg( |ln( |Log_O(| O³ | |K7 |- |/ |AC |S<->D|cos( |Log( |O⁻¹ |MODE | With that mapping and the [keys sequence](link) needed to make the hack work I made the above mentioned file *pin_template.h* ## Compiling and burning the code To compile the code is necessary to have an installed Arduino (1.0 by now) environment, and CMake build system. To compile this code I'm using [queezythegreat's Arduino CMake](https://github.com/queezythegreat/arduino-cmake). Steps: ``` bash #Get the source code git clone https://samkpo@bitbucket.org/samkpo/calculadora.git #Go to the source code folder cd code #Generate key comb file if necessary (only if you edited the template) ./config_key.sh #Make build directory, and go to it mkdir build && cd build #Invoke CMake cmake .. #Compile make #Burn code make CHack-burn ``` And it should be flashing. To burn the code I use the guide described in the Arduino page [Arduino ISP](https://www.arduino.cc/en/Tutorial/ArduinoISP), and the Arduino board (atmega328bb) was set using the [Arduino to Breadboard](https://www.arduino.cc/en/Tutorial/ArduinoToBreadboard) tutorial, section _Minimal Circuit (Eliminating the External Clock)_.