/* RPN calculator code. * * A standard four-function calculator nowadays, like a Kenko KK-402, * has digits 0-9, a decimal point, binary operators +-×÷, an ON/C * button, maybe an OFF button, an = button, √, %, and for its memory * cell, M+, M-, and MR/C. * (If they have a +/- or CHS key I’m missing it.) * This is a total of 18 or 19 keys. * Typically they have 8-digit or 12-digit 7-segment LCD displays with * a decimal point after each digit, plus M, -, and E indicators. * * What if you rebrain that hardware with an AVR, MSP430, or similar * microcontroller? At the very least, you could give it a more * reasonable user interface. You’d probably need 11 GPIO lines for * the keyboard and 16 for the LCD, although maybe they could be the * same lines — in the keyboard case you’re never driving more than * one line at a time, although it would be bad if pressing keys * messed with screen pixels. * * This is a first cut at what the internal logic of a simple * four-function RPN calculator would look like. This version of the * code is 792 bytes compiled with avr-gcc -Os -std=gnu99, which is 3% * of the capacity of an ATMega328, 19% of the capacity of a 76¢ * ATTiny40, and about 5% of the capacity of the popular 260¢ 24-GPIO * MSP430G2553IRHB32R, which might be a better match due to its lower * power consumption. * * XXX now it’s 2134 bytes on AVR and the decimal library is 1204 * bytes — something went wrong! But, hey, that still fits in just * about any microcontroller. I think the particular problem is that * it’s copying around decimals by value all over the place, and those * are 6 bytes. * * Currently has a couple of problems: * * - For the rebraining not to be a downgrade, you’d need √ and some * kind of stack manipulation. The remaining buttons for such * functions are = (equivalent of ' '?), √, %, M+, M-, and MR/C. * - Numbers can scroll off the left side of the display if they have * a lot of decimals. Some kind of scrolling solution is needed; * maybe M+ and M- could be scrolling keys, and maybe Ξ or _ or something * could indicate the need for scrolling. * - You only see 7 digits if a decimal point is displayed, which is * not a good simulation of a calculator display. * - Typing in a lot of digits will give rise to overflow errors, as * will ordinary math overflow errors (e.g., `100000 100000*`). * More disturbingly, the floating-point representation gets * overflow errors very easily: `1.00101 1.00101*`, for example. * - For the rebraining to be a really big upgrade, I could add * persistent memory and a menu system with many more functions. * For example, you could have a menu key like % which takes you to * a menu screen from which you can select functions like ln, exp, * sin, cos, and tan; and perhaps repeating the menu key would take * you to different menus; but also you could give it undo, make it * programmable, and perhaps give it some solvers, automatic * differentiation, and optimizers. A complicated thing about the * menus is that there are only 8 or 12 digits on the display, which * is not very much for displaying a menu for 18 or 19 keys. */ #include #include "rpncalc.h" #include "decimal.h" typedef int8_t s8; typedef uint8_t u8; enum { true = 1, false = 0, stack_size = 8, screen_size = 8 }; typedef decimal number; typedef struct { number stack[stack_size]; u8 stack_items, entering, entering_fraction; } rpn_state; typedef struct { char *buf; u8 max; u8 len; } outbuf; static u8 is_full(outbuf *b) { return b->len == b->max; } static u8 say(outbuf *b, char c) { if (is_full(b)) return 0; b->buf[b->len++] = c; return 1; } static void discard(rpn_state *st) { for (int i = 0; i != stack_size-1; i++) { st->stack[i] = st->stack[i+1]; } st->stack_items--; } static void push(rpn_state *st, number n) { if (st->stack_items == stack_size) { discard(st); } st->stack[st->stack_items] = n; st->stack_items++; } static number pop(rpn_state *st) { if (0 == st->stack_items) return dec_of_int(0); st->stack_items--; return st->stack[st->stack_items]; } static u8 num_can_remove_digit(number n) { return !n.is_error && n.mantissa > 9; } static void process_key(rpn_state *st, rpn_key key) { u8 entering = st->entering; st->entering = false; number m = pop(st); u8 n = key - '0'; if (n <= 9) { if (!entering) { push(st, m); st->entering_fraction = false; m = dec_of_int(0); } st->entering = true; decimal result = { .mantissa = m.mantissa * 10 + n, /* XXX overflow */ .decimals = st->entering_fraction ? m.decimals + 1 : 0, }; push(st, result); return; } if ('.' == key) { if (!entering) { push(st, m); m = dec_of_int(0); } st->entering = st->entering_fraction = true; /* fall through to repush m */ } if ('+' == key) { push(st, dec_add(pop(st), m)); return; } if ('-' == key) { push(st, dec_sub(pop(st), m)); return; } if ('*' == key) { push(st, dec_mul(pop(st), m)); return; } if ('/' == key) { push(st, dec_div(pop(st), m, 4)); return; } if ('\b' == key) { if (entering && num_can_remove_digit(m)) { decimal result = { .mantissa = m.mantissa / 10, .decimals = m.decimals ? m.decimals - 1 : 0, }; push(st, result); if (!result.decimals) st->entering_fraction = false; st->entering = true; } return; } push(st, m); } /* convert (a number) to ASCII bytes, backwards */ static void cvs(outbuf *buf, number n) { if (n.is_error) { say(buf, 'E'); return; } if (n.mantissa < 0) { cvs(buf, dec_sub(dec_of_int(0), n)); say(buf, '-'); return; } int32_t mantissa = n.mantissa; for (int i = 0; mantissa != 0 || i < n.decimals+1; i++) { if (i > n.decimals - screen_size) { /* XXX this sux */ if (!say(buf, '0' + mantissa % 10)) break; if (i == n.decimals - 1) say(buf, '.'); } mantissa /= 10; } } static void reverse(char *buf, int n) { for (int i = 0; i < n - i - 1; i++) { char tmp = buf[i]; buf[i] = buf[n - i - 1]; buf[n - i - 1] = tmp; } } static void draw_state(rpn_state *st) { if (st->stack_items == 0) { rpn_set_screen("-0-", 3); } else { char buf[screen_size]; outbuf b = { buf, screen_size, 0 }; s8 i = st->stack_items - 1; while (!is_full(&b) && i >= 0) { cvs(&b, st->stack[i--]); if (i >= 0) say(&b, ' '); } reverse(buf, b.len); rpn_set_screen(buf, b.len); } } int main() { rpn_state state = { 0 }; rpn_key key; rpn_setup(); for (;;) { draw_state(&state); if (!(key = rpn_getch())) break; process_key(&state, key); } rpn_cleanup(); return 0; }