Go Down

Topic: Plz help to reduce code size (and even speed it up) (Read 417 times) previous topic - next topic

deetee

Hi everybody!

I'm programming a scientific RPN-calculator for an ATTINY85 and a QYF-TM1638-board (8 digit LED display with 16 buttons).

So far the prototype works surprisingly good and is rich of features.

Now I ran out of memory (code size > 8k):
The attached code produces 7642 bytes and I want to add at least trigonometric functions (need approximately 1500 bytes) and if possible hyperbolic functions, some screensaver/sleep functions and other mathematical functions (ie. convert polar to rectangular coordinates and vice versa).

I spent many hours in avoiding libraries and followed many "code resizing hints". But I'm not familiar what really costs bytes. So far I see only few possibilities to save code and can not evaluate if it is worth to go this ways:
* Reprogram EEPROM.h (did not find hints how to code this)
* Using few flag variables (instead of boolean-bytes) and manipulate their bits (&|~).
* Reducing the number of used mathematical functions (i.e. exp(0.5*log(x)) instead of sqrt(x))
* Using own written taylor series instead of mathematical functions

I would really appreciate any idea or hint to save bytes.
As the code is "slow-moving" on the ATTINY I would appreciate hints for speeding up too.

Thanks in advance for any help and regards
deetee







PaulS

Quote
Reprogram EEPROM.h
For what purpose?

Quote
Using few flag variables (instead of boolean-bytes) and manipulate their bits (&|~).
That may reduce SRAM requirements, at the cost of more flash space.

Quote
Reducing the number of used mathematical functions (i.e. exp(0.5*log(x)) instead of sqrt(x))
I instantly recognize what sqrt() does. I have to think about whether exp(0.5*log(x)) does the same thing. YMMV.

Quote
Using own written taylor series instead of mathematical functions
You think you are a better programmer than the person that wrote sqrt() or exp() or sin() or cos()?
The art of getting good answers lies in asking good questions.

-dev

You might try switching from the built-in digitalWrite and shiftOut to Mikael Patel's GPIO library.  It is much faster and smaller.  It has a derived SRPIO class that implements I/O shift registers.  The declaration would look like this:

Code: [Select]
#include <GPIO.h>
#include <SRPIO.h>

GPIO<BOARD::D9>    STROBE; // Strobe pin // Arduino/Genuino Micro
const BOARD::pin_t CLOCK  = BOARD::D8; // Clock pin
const BOARD::pin_t DATA   = BOARD::D7; // Data pin
//#define DATA   2 // 5 green    // ATTINY85
//#define CLOCK  1 // 6 yellow   // HW-pins: 8 7 6 5  SW-pins: 3 2 1 0
//#define STROBE 0 // 7 orange   //          1 2 3 4           4 5 6 7

SRPIO<LSBFIRST, DATA, CLOCK> display;  // Assumes DATA pin has an external pull-up resistor

... and the usage looks like this:

Code: [Select]
void cmd(byte val) { // Send command to shield
  STROBE.low();
    display.write( val );
  STROBE.high();
}

This saves more than 800 bytes of program space (see attached).

Quote
As the code is "slow-moving" on the ATTINY I would appreciate hints for speeding up too.
You should get rid of the delay in getKey.  Instead, use a polling technique like that used by the Bounce2 library.  Save a millis timestamp when the keys change, then compare that timestamp later to see if the keys stabilize.  To understand the concept, read these:

Hackaday's a nice summary

Ganssle's description

this one

Cheers,
/dev
Really, I used to be /dev.  :(

deetee

Hello -dev!

Thanks for your fast and very professional response.

You gave me new hope to manage my challenge - even if your view is a totally new area for me and will give me lots to do for the next weekends :-)

Regards
deetee

outsider

#4
Jan 17, 2018, 09:18 pm Last Edit: Jan 17, 2018, 09:40 pm by outsider
You could put seldom used CONSTANTS and error message strings in EEPROM, it's a lot slower to retrieve data than from sram or flash, also frequently WRITING to EEPROM will wear out the cells, but READING has no (or little) effect.

deetee

@outsider:
Thanks for this hint. I planned some error messages- and to store this text in EEPROM (like I did with physical constants) is a good idea.

@dev:
I tried your GPIO-hint and it seems really to save some hundreds of bytes. Unfortunately the I/O (printbuf and getbuttons) do not work at all. I found 2 reasons why this could be:
* I had to install delay_basic.h (from avr-libc-master) to get GPIO compiled.
* A comment says that SRPIO assumes a pull-up resistor on the DATA pin. I am not so familiar with this - do I have to connect the DATA-pin with Vcc with a resistor (ie 10k)?

What do you mean? Do you see other reasons why I/O do not work?

TIA
deetee

-dev

Quote from: deetee
* I had to install delay_basic.h (from avr-libc-master) to get GPIO compiled.
Hmm... The build for an UNO seemed to work fine, but the ATtiny x5 build did not.  It probably doesn't matter how you got it to compile, because delay_loop_2 is only used by the pulse method.  You don't use that method, so it's not even linked in.

If you want to investigate this further, you'll need to post the exact sketch, the errors you get, and the core you are building with.

Quote
* A comment says that SRPIO assumes a pull-up resistor on the DATA pin. I am not so familiar with this - do I have to connect the DATA-pin with Vcc with a resistor (ie 10k)?
Yes.  When it is possible for two devices to "transmit" at the same time, you should avoid the possibility for one device to write a HIGH while another device writes a LOW.  The SRPO class only writes a LOW.  For transmitting a HIGH, it puts the DATA pin in INPUT mode, *assuming* that an external resistor "pulls" the wire to a HIGH state.  If all devices take that approach (aka open-drain), they will never "fight" over whether the line should be HIGH or LOW.  Devices drive LOW, resistor pulls HIGH.

If you really don't want to do that, I could describe the mods to the SRPO class.  I wouldn't be able to try it, of course.
Really, I used to be /dev.  :(

deetee

My progress so far:

* GPIO/SRPIO
I tried /dev's code with a pullup resistor (10k between DATA-pin and Vcc) - but unfortunately without success. See attached code. This method could save me 400 bytes - so that is the most promising way to reduce the code size.

* main/init/while instead of setup/loop
To replace the setup/loop code with following main/init/while-construction saves me 90 bytes. Unfortunately I instantly loose the serial connection after upload and I have to reset (connect GND and RST) the arduino to upload again. Uncomfortable, but it works - and finally I will not need a serial connection for my calculator.

Code: [Select]

int main(void) {
  init();
  {
    // Setup code
  }
  while(1) {
    // Loop code
  }
}



TIA for any hint/help
deetee

-dev

Quote
* GPIO/SRPIO
I tried /dev's code with a pullup resistor (10k between DATA-pin and Vcc) - but unfortunately without success.
Here is a SRPIO2 class that does not require the pullup resistor.  It forces the data line to output mode whenever it writes a value, then leaves the data line in input mode.  If read is ever called, it is still in input mode.  Matching sketch attached.

If this still isn't working, you might try sending each written or read byte to Serial as a HEX value, just to make sure I didn't mess up the calls.  I would think the STROBE line code is ok.

If you have a scope or logic analyzer, this would be a good time to take a look.  A second arduino could also be used to double check what the sketch is doing.
Really, I used to be /dev.  :(

robtillaart

#9
Jan 19, 2018, 07:35 pm Last Edit: Jan 19, 2018, 07:37 pm by robtillaart
Speed up

Code: (snippet) [Select]
double pow10(int8_t e) { // Returns 10^e
  double f = 1.0;
  for (byte i = 0; i < abs(e); i++) e >= 0 ? f *= 10 : f /= 10;
  return (f);
}


==>  almost 2x faster (similar size)

Code: (snippet) [Select]
double pow10(int8_t e)  // Returns 10^e
{
  bool ne = (e < 0);  // negative exponent
  double f = 1.0;
  for (byte i = abs(e); i > 0; i--) f *= 10 ;
  if (ne) f = 1/f;
  return (f);
}


==> almost 3x faster (but definitely bigger)

Code: (snippet) [Select]
double pow103(int8_t e)  // Returns 10^e
{
  bool ne = (e < 0);  // negative exponent
  double f = 1.0;
  byte i = abs(e);
  while ( i >= 3) { i-=3; f *= 1000; }
  while ( i-- > 0) f *= 10 ;
  if (ne) f = 1 / f;
  return (f);
}


division is more expensive than multiply.
big multiplies are faster than small multiplies.
(speed ups tested on an 328)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

deetee

Hi all!

@dev
Thanks for making another library which doesn't need a pullup-resistor. Unfortunately the compiler gives an error
   "SRPIO2 does not name a type"
when compiling the line
   SRPIO2<LSBFIRST, DATA, CLOCK> display;
(??)

@robtillaart
That is very kind of you to optimize and test my subroutines. I am going to use your second solution which is fast and short. Unfortunately my program slows down when using menus. Due to a hint of /dev I could speed it up when getting rid of the DEBOUNCE-delay in the getkey-routine. And I think I have to review/rebuild the menu-routines (functions, physical constants) too.

Regards
deetee

deetee

Hi all!

@dev
I found a typo when linking to SRPIO2.h
Now it compiles - but without success (nothing to see on the display and no reaction when reading a val from keyboard).

Sorry that I am not able to help myself but I am not familiar with this kind of software (and I don't own things like a logic ananalyzer).

Regards
deetee

-dev

Quote
I found a typo when linking to SRPIO2.h
Now it compiles - but without success (nothing to see on the display and no reaction when reading a val from keyboard).
Ok, here's a version of SRPIO that leaves the data pin in the output state.  It is possible that SRPIO2 switched to the input state too soon after clocking out the last bit written.  Maybe the "hold time" on that last bit was too short.

With this file, change the include statement and the template instantiation line in your sketch to "SRPIO3".

Cheers,
/dev
Really, I used to be /dev.  :(

deetee

Hello /dev!

Thank you very much for your patience and work - but still no success.

Unfortunately I can not tell what's wrong. Compiling is done but the display remains dark and reading the keyboard (val) remains unchanged when typing.

Right now I am working on other frontiers (will post the code when finished):
* Using the EEPROM for storing menus and constants (doesn't work on ATTINY ??)
* Writing subroutine for trigonometric functions sin and atan (similar Taylor series) - other trigonometric functions can be calculated with sin or atan.
* Rewriting menu selection routine.

Unfortunately this slows down the speed on the ATTINY dramatically :-(

Regards
deetee

deetee

Hello /dev!

At least I found out that display.write (shiftOut-substitute) confuses the board completely - even forgets the Port, so I have to reset the arduino by hand (connect RST with GND).

Setting ports (STROBE.low() and STROBE.high()) seem to work.

Maybe that helps.

I found out that the regular shiftIn-command slows down the code extremely. So your code will help me double - if it works.

Regards
deetee

Go Up