3

First of all I want to be clear, I'm not very skilled in programming methods, all the C I learned was for various Arduino projects.

That being said, I just finished a project that reads some data from ELM327 via a HC-05 Bluetooth module and prints it to a 3.5 inch screen. All is working as intended, but apparently I have capped my Arduino UNO's memory because the sketch utilizes 99% of it. At the moment this is not very troublesome, but if sometime in the future I want to add more data to be read, I won't be able to do it.

With this in mind, I was looking for some advice on how I could rewrite my code in order to occupy less memory. I tried some stuff, but had little effect. As I said, I'm not familiar with advanced programming techniques.

LE: So I used all of your advice, I got rid of all the fonts, modified the MCUFRIEND library to only work with my display and got rid of some repeating code, and now the sketch got 35% smaller. Thanks to all.

Sketch uses 21216 bytes (65%) of program storage space. Maximum is 32256 bytes. Global variables use 642 bytes (31%) of dynamic memory, leaving 1406 bytes for local variables. Maximum is 2048 bytes.

Here's the (old) code:

#include <SoftwareSerial.h> 
#include <Adafruit_GFX.h> 
#include <MCUFRIEND_kbv.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include <Fonts/FreeMonoBold24pt7b.h>

SoftwareSerial BTSerial(10, 11);
MCUFRIEND_kbv tft;

#define BLACK   0x0000
#define WHITE   0xFFFF
#define GREY    0x8410

byte inData;
char inChar;
String constructor="";
String deconstructor="";
int A;
int B;
bool busy = false;

void setup()
{
    Retry:
    Serial.begin(9600);
    BTSerial.begin(38400);
    delay(1000);

    BTSerial.println("0100");
    while(BTSerial.available()>0)
    {
      inData=0;
      inChar=0;
      inData = BTSerial.read();
      inChar=char(inData);
      constructor = constructor + inChar;
      delay(20);
   }
    if (constructor.length()>0)
    {
      Serial.println("Conectat");
    }
    else { 
      goto Retry;
      Serial.println("retry");
    }
    delay(5000);
    ReadData();
    basicConditions();

    tft.reset();
    uint16_t identifier = tft.readID();
    if (identifier == 0xEFEF) identifier = 0x9486;
    tft.begin(identifier);

    tft.fillScreen(BLACK);
    tft.setRotation(1);
    tft.setTextColor(WHITE);

    tft.fillRect(10, 40, 70, 40, GREY);
    tft.fillRect(10, 140, 70, 40, GREY);
    tft.fillRect(10, 240, 70, 40, GREY);
    tft.fillRect(140, 70, 200, 100, GREY);
    tft.fillRect(150, 220, 180, 60, GREY);
    tft.fillRect(370, 40, 70, 40, GREY);
    tft.fillRect(370, 140, 70, 40, GREY);
    tft.fillRect(370, 240, 70, 40, GREY);

    tft.setCursor(170,10);
    tft.setFont(&FreeMonoBold12pt7b);
    tft.print("Dacia 4X4");

    tft.setCursor(30,35);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("Cuplu");
    tft.setCursor(80,70);
    tft.setFont(&FreeMonoBold18pt7b);
    tft.print("%");

    tft.setCursor(15,120);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("Presiune"); 
    tft.setCursor(15,135);   
    tft.print("motorina");
    tft.setCursor(82,170);
    tft.setFont(&FreeMonoBold12pt7b);
    tft.print("bar");

    tft.setCursor(15,220);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("Presiune"); 
    tft.setCursor(20,235);   
    tft.print("admisie");  
    tft.setCursor(82,270);
    tft.setFont(&FreeMonoBold12pt7b);
    tft.print("bar");  

    tft.setCursor(395,20);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("Temp."); 
    tft.setCursor(382,35);   
    tft.print("Antigel");
    tft.setCursor(450,70);
    tft.setFont(&FreeMonoBold12pt7b);
    tft.print("C"); 

    tft.setCursor(395,120);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("Temp."); 
    tft.setCursor(382,135);   
    tft.print("Admisie");
    tft.setCursor(450,170);
    tft.setFont(&FreeMonoBold12pt7b);
    tft.print("C"); 

    tft.setFont(&FreeMonoBold9pt7b);
    tft.setCursor(370,235);   
    tft.print("Debit Aer");
    tft.setCursor(445,270);
    tft.setFont(&FreeMonoBold9pt7b);
    tft.print("g/s");     

    tft.setFont(&FreeMonoBold12pt7b);
    tft.setCursor(150, 62);   
    tft.print("Turatie (RPM)");

    tft.setFont(&FreeMonoBold12pt7b);
    tft.setCursor(152, 212);   
    tft.print("Viteza (KM/H)");



}

void loop()
{
  cuplu();
  PresMot();
  PresAdm();
  TempAntig();
  TempAdm();
  MAF();
  RPM();
  viteza();
}
void basicConditions(){
    if(constructor.indexOf(">")>0)  {
    busy=false;
    constructor="";
  }
}
void ReadData()
{   
  if (BTSerial.available()>0)
    busy = true;
  while(busy = true && constructor.indexOf(">")<0)
  {
    if (BTSerial.available()>0){
    inData=0;
    inChar=0;
    inData = BTSerial.read();
    inChar=char(inData);
    constructor = constructor + inChar;
    //Serial.println(constructor);
    //Serial.println(busy);
  }
  }
  //Serial.println("done");

  if (constructor.indexOf(">")>0){
    deconstructor=constructor.substring(11,13);
    A = strtol(deconstructor.c_str(),NULL,16);
    if (constructor.length()>15) {
        deconstructor = constructor.substring(14,16);
            B = strtol(deconstructor.c_str(),NULL,16);
    }
    else B = 0;
  }
}
void cuplu() {
  if (busy == false){
    BTSerial.println("01041");
      ReadData();
    tft.setTextSize(1);
    tft.setFont(&FreeMonoBold18pt7b);
    tft.setCursor(10, 70);
    tft.fillRect(10, 40, 70, 40, GREY); 
    tft.print(A/2.55, 0);   
  }
  basicConditions();
}
void PresMot() {
  if (busy == false){
      BTSerial.println("010A1");
      ReadData();
      tft.setTextSize(1);
      tft.setFont(&FreeMonoBold18pt7b);
      tft.setCursor(10, 170);
    tft.fillRect(10, 140, 70, 40, GREY); 
    tft.print((3*A)/100.0, 1);
  }
  basicConditions();
}
void PresAdm() {
  if (busy == false){
      BTSerial.println("010B1");
      ReadData();
      tft.setTextSize(1);
      tft.setFont(&FreeMonoBold18pt7b);
      tft.setCursor(10, 270);
    tft.fillRect(10, 240, 70, 40, GREY); 
    tft.print((A/100.0), 1);
  }
  basicConditions();
}
void TempAntig() {
    if (busy == false){
      BTSerial.println("01051");
      ReadData();
      tft.setTextSize(1);
      tft.setFont(&FreeMonoBold18pt7b);
      tft.fillRect(370, 40, 70, 40, GREY);
    tft.setCursor(370, 70);
    tft.print(A-40);
    }
  basicConditions();
}
void TempAdm() {
    if (busy == false){
      BTSerial.println("010F1");
      ReadData();
      tft.setTextSize(1);
      tft.setFont(&FreeMonoBold18pt7b);
      tft.fillRect(370, 140, 70, 40, GREY);
    tft.setCursor(370, 170);
    tft.print(A-40);
    }
  basicConditions();
}
void MAF() {
  if (busy == false){
      BTSerial.println("01101");
      ReadData();
      tft.setTextSize(1);
      tft.setFont(&FreeMonoBold18pt7b);
      tft.fillRect(370, 240, 70, 40, GREY);
    tft.setCursor(370, 270);
    tft.print(((256*A)+B)/4.0, 0);  
  }
  basicConditions();
}
void RPM() {
  if (busy == false){
      BTSerial.println("010C1");
      ReadData();
      tft.setFont(&FreeMonoBold18pt7b);
    tft.setTextSize(2);
    tft.fillRect(140, 70, 200, 100, GREY);
    tft.setCursor(150, 140); 
    tft.print(A/4.0, 0);
  }
  basicConditions();
}
void viteza() {
  if (busy == false){
      BTSerial.println("010D1");
      ReadData();
      tft.setFont(&FreeMonoBold18pt7b);
    tft.setTextSize(2); 
      tft.fillRect(150, 220, 180, 60, GREY);
    tft.setCursor(180, 270); 
    tft.print(A);
  }
  basicConditions();
}
4
  • 99% of code is no problem, 80% dynamic ram can be a problem. What is the percentage of the used dynamic ram? The gfx library and the fonts use a lot of memory. You can upgrade to a arduino mega for more ram and extra hardware serial ports, or upgrage to arduino zero or one of the mkr boards for a faster processor, more ram and extra hardware serial port.
    – Jot
    Aug 17, 2018 at 7:13
  • Are you trying to reduce Flash (code-space) or RAM (data-space) use? Which one is at 99%? The techniques for reducing either are different.
    – JRobert
    Aug 17, 2018 at 12:16
  • It says that the sketch size is at 99% and the dynamic memory is at 30%. So I’m guessing that the code size is problematic. Aug 17, 2018 at 16:26
  • the code for drawing the gray rectangles is in your sketch twice
    – jsotola
    Aug 18, 2018 at 2:32

4 Answers 4

3

I assume you mean that are running short of flash memory, rather than RAM. I base this assumption on the fact that you say that it's 99% full, and that this “is not very troublesome”. Filling 99% of the RAM with statically allocated data would certainly be troublesome. But please note that it as always better to make your question clear and explicit, rather than expect the respondents to make guesses.

Now, look at your font files. Each one of them ends with a comment similar to this:

// Approx. 1672 bytes

Just adding those numbers we get:

FreeMonoBold9pt7b   1672 bytes
FreeMonoBold12pt7b  2402 bytes
FreeMonoBold18pt7b  4485 bytes
FreeMonoBold24pt7b  7469 bytes
------------------------------
TOTAL              16028 bytes

That's about 49% of your Arduino's flash. So that's the first place you should be looking to if you want to save flash. Do you really need these four different font sizes? And more specifically, do you really need the (very flash hungry) largest sizes? Getting rid of some of them is the easiest way to save flash.

If you really want to keep them all, your next option is to trim the fonts in order to have only the characters you are actually using. This can be a lot of work, but it should be a very effective way of significantly reducing their flash footprint.

If you decide to take this path, the first step is to take note of what characters you need at each font size. From a cursory look at your source, it seems to me that the 18 pt font is only used for printing “%”, “.”, the ten digits, and possibly “-”. That's a lot less than the full ASCII repertoire the font supports. And I can't see where you are using the 24 pt font. Look into an ASCII table and take note of the smallest continuous range that contains all the characters you use at one font size. Then trim the Glyphs array of the font to keep only that range, and fix the first and last fields of the GFXfont struct accordingly.

Next step is what will take most of the work. For each character you actually need, take note of the bitmapOffset field of its glyph and the next one. This way you know the range of offsets that are used by that character. Then remove from the font's Bitmaps array all the bytes that are at offsets you don't need. While doing this, you will be changing the offsets of the bytes you keep, so you should update the bitmapOffset fields on the Glyphs array accordingly.

Yes, that's quite some work, but you should end up with custom fonts that are a lot lighter than the original ones, and yet capable of displaying everything you are displaying now. Maybe you could write a program to automate all this. Maybe someone already did that and you can find it through a Web search...

1
  • Thanks for the advice and sorry I wasn’t very clear about the problem. First thing I’ll do is to get rid of the fonts permanently and go from there. Aug 17, 2018 at 16:30
4

The documentation for the MCUFRIEND_kbv project has a file describing how to use.

This document has the statement:

Most of these controllers are #define SUPPORT_xxxx by default.
You can save Flash memory on a Uno by commenting out the macro(s) in MCUFRIEND_kbv.cpp

So you open MCUFRIEND_kbv.cpp and comment out support for the controllers you aren't using.

You can also reduce the number of different fonts you use to save Flash space.

0
1

Strings get allocated, reallocated, and deallocated as you use them, which can leave the heap - the bottom end of RAM memory - fragmented and the fragments essentially unusable. The memory report from the compiler can't show this; it happens at runtime.

If instead, you pre-allocate character arrays of a sufficient size for the eventual maximum text you want to put in them, they will stay in one place, neither growing nor shrinking, but also won't create those unused but unusable "holes" in the RAM.

Update:

Instead of:

String xyz;
xyz = "Hello";
xyz = xyz + ',';
xyz = xyz + "World!\n";

, you'd write:

#define XYZ_MAX  25    // or whatever size you need

char xyz[XYZ_MAX+1];
strcpy(xyz, "Hello";      // overwrites whatever is there
strcat(xyz, ", World!\n"; // appends whatever is there

You can further optimize RAM by directing the compiler to store the string literal ("Hello", etc.) in Flash. That's another topic, but here is a link to an article about it in the Arduino Playground, to get you started.

1
  • This is something I wasn't aware of. Maybe you could give me a short example on how to do it exactly. Aug 17, 2018 at 21:18
0

you are repeating the same code in the 8 functions at the end of your sketch (note: the code is badly indented and the command sequence is inconsistent between functions)

similar thing is occurring at the middle of your sketch

here is the data that repeats

you could use one function and a lookup table (array)

this is the repeating data

+---------------+-------+-------------------------------+---------------+-----------------------+
|  println     setTextSize        fillRect                 setCursor          print             |
+---------------+-------+-------------------------------+---------------+-----------------------+
|   "01041"     |   1   |    10,  40,  70,  40, GREY    |    10,  70    |   A/2.55         , 0  |
|   "010A1"     |   1   |    10, 140,  70,  40, GREY    |    10, 170    |   (3*A)/100.0    , 1  |
|   "010B1"     |   1   |    10, 240,  70,  40, GREY    |    10, 270    |   (A/100.0)      , 1  |
|   "01051"     |   1   |   370,  40,  70,  40, GREY    |   370,  70    |   A-40                |
|   "010F1"     |   1   |   370, 140,  70,  40, GREY    |   370, 170    |   A-40                |
|   "01101"     |   1   |   370, 240,  70,  40, GREY    |   370, 270    |   ((256*A)+B)/4.0, 0  |
|   "010C1"     |   2   |   140,  70, 200, 100, GREY    |   150, 140    |   A/4.0          , 0  |
|   "010D1"     |   2   |   150, 220, 180,  60, GREY    |   180, 270    |   A                   |
+---------------+-------+-------------------------------+---------------+-----------------------+

you could also calculate some of the values in the first 6 functions

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.