Scientific calculator hack - CHack


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.

  2. Find an ic that acted as an electronic key. Done: CD4066.

  3. Write some Arduino code and test it. It worked.

  4. Optimize the code.

  5. Shrink everything and make a PCB.

Project probes

Here is a photo of the prototype:


And here a link to YouTube 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 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

3D view of the front of the board 3D view

On a side: 3D view

The back: 3D view

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 :-|


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 library provided such functionality. For timming I used the watchdog interrupt, avoiding to use the μ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, 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 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 μ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 module has a command to generate just that.

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 μ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:

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 μ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: - #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:

//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:

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 (or Github mirror):

  • 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:

//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:

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

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


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:


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). The map is kinda like this:

For the k:

k1 PORTB 3
k2 PORTC 5
k3 PORTC 4
k4 PORTC 3
k5 PORTC 2
k6 PORTC 1
k7 PORTC 0

And the ki:

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 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. 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) Alpha
K3 3 6 9 ( hyp < ˄
K4 + x DEL ) sin( O^O ˅ >
K5 = Ans x10 . 0
K6 M+ tg( ln( Log_O(
K7 - / AC S<->D cos( Log( O⁻¹ MODE

With that mapping and the keys sequence 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.


#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)

#Make build directory, and go to it
mkdir build && cd build

#Invoke CMake
cmake ..


#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, and the Arduino board (atmega328bb) was set using the Arduino to Breadboard tutorial, section Minimal Circuit (Eliminating the External Clock).