[SOLVED] How do I isolate different parts of my source code?

I'm writing a library for using cheap 433/315MHz RF modules with ATtiny13.

My file structure is as follows:

--Arduino
----Libraries
------TinyRF
--------TinyRF.h
--------TinyRF.cpp
--------TinyRF_TX.h
--------TinyRF_TX.cpp
--------TinyRF_RX.h
--------TinyRF_RX.cpp

(Files are available in this gist).

TinyRF_TX.cpp: Is responsible for transmitter functionality. It is intended to be uploaded to an ATtiny13 so I have tried to make it as small as possible.
TinyRF_RX.cpp: Is responsible for receiver functionality. It is intended to be uploaded to an Arduino, it has interrupts, buffers, and uses considerably more memory/cpu.
TinyRF.cpp: For functions and constants that are shared between the RX and TX such as CRC function and timings. Both RX and TX need to have access to this.

Each file consists of global variables and functions. I'm not using classes because I suppose they have overhead and I didn't want even one extra byte because of the 1KB memory of the tiny.
My idea was that you include TinyRF_TX.h in your sketch and the compiler would not compile anything from TinyRF_RX because it's never included.

But it turns out that when you compile the project all cpp files get compiled, even if they are never included, which means everything global from the receiver files get copied into the ATtiny. And the tiny of course cannot handle that.

I thought maybe I should use a class to encapsulate the receiver data inside an instance, but it turns out you cannot give a class member to attachInterrupt(). Also, even if I did manage to use the member function as an interrupt it would still be useless because I need access to many of the instance variables inside my interrupt, which means I would have to make the instance global which gets me back to the problem in the first place.

The only thing I can think of is creating two separate libraries one for TX and one for RX but:
1- It would be extremely ugly. If someone wanted to use it they would have to download/include two different libraries.
2- I have a lot of shared code for timing,etc. and then I'd have to update them in each library every time I change anything.

I really need a way to exclude the RX code from being injected into my TX code.
It seems trivial but I can't think of anything. Can anyone help?

But it turns out that when you compile the project all cpp files get compiled, even if they are never included, which means everything global from the receiver files get copied into the ATtiny. And the tiny of course cannot handle that.

Not sure what you are saying there... Functions that are included but never called don't make it to the final binary. And code that is not included has no reason to be compiled...

J-M-L:
Not sure what you are saying there... Functions that are included but never called don't make it to the final binary. And code that is not included has no reason to be compiled...

This is what happens:
Code size with the directory structure I showed: 712 bytes of flash, 83 bytes of ram.
Code size with volatile variables removed from TinyRF_RX.cpp: 712 bytes of flash, 42 bytes of ram.
Code size when TinyRF_RX.h and TinyRF_RX.cpp are deleted: 634 bytes of flash, 40 bytes of ram.

I need those variables to be volatile inside TinyRF_RX.cpp because they're used in the interrupt.
Nothing related to RX is used/included and deleting it doesn't give a compile error. It just reduces code size.

what's your test code example ?

J-M-L:
what's your test code example ?

Test code is this:

#include "TinyRF_TX.h"

const uint8_t pin = 2;
uint8_t index = 0;
const int dataSize = 2;
const char* stuff[dataSize] = {"abcdabcdabcdabcd", "abcdabcdab"};


void setup() {
  setupTransmitter(pin);
}

void loop() {
    send((byte*)stuff[index], strlen(stuff[index])+1);
    index++;
    if(index >= dataSize){
    index = 0;
    }
    delay(2000);
}

I also created a gist with my library files here:

What is the point of writing a library for such an extremely limited microprocessor? Do you imagine someone else would be interested in using it?

You need to save every byte of program memory that you possibly can, and if you are already having trouble fitting it in, how will you add code to make it do something useful, like process and send sensor data?

jremington:
What is the point of writing a library for such an extremely limited microprocessor?

Feeling good about myself and making the most out of this limited but very cool microprocessor.
I've made an RGB lamp with one. You can control it with a TV remote, it has 2 different modes and you can even change its speed. All in 1KB. I find that very pleasing.

Do you imagine someone else would be interested in using it?

They won't? :cry:

I'm writing it mainly for myself, so for future use all I would have to do is include the header. I have a project in mind which will need this.
But there has to be other people like me. I cannot be the only one.

You need to save every byte of program memory that you possibly can.

I don't see any overhead in including the library files. Is there an overhead?

Evidently we have differing ideas on the meaning of the term "library".

I don't see any overhead in including the library files.

Compared to what?

I have self-written libraries that nobody might ever see. I don't see anything abnormal about it. It's true, you need to be very careful to avoid any overhead with such a small processor. There is no inherent overhead with a library, but the way they are usually constructed might be slightly inefficient.

Functions that are included but never called don't make it to the final binary. And code that is not included has no reason to be compiled

Which "core" are you using to compile the code, and have you confirmed that it sets the proper link and compiler switches to gain the most optimization? (enable "link-time optimization" (-flto) in the boards settings, if you haven't...)

When you say "library" - have you set it up as an Arduino library, or are you just throwing those source files into your sketch directory?

I looked at it some more, and I can't figure out what the problem is.
Probably all those "volatile" qualifiers prevent optimization?

BTW, "attachInterrupt()" is pretty awful WRT to code size, RAM usage, AND execution speed.

jremington:
Evidently we have differing ideas on the meaning of the term "library".
Compared to what?

By library I mean one or more header files with their corresponding .ccp files that you include in your code which enables you to use the functions/classes defined by them.

westfw:
Which "core" are you using to compile the code, and have you confirmed that it sets the proper link and compiler switches to gain the most optimization? (enable "link-time optimization" (-flto) in the boards settings, if you haven't...)

I'm using MicroCore: GitHub - MCUdude/MicroCore: A light-weight Arduino hardware package for ATtiny13
It has pretty extreme optimization so I don't think that's the problem.
The options in board settings are:

attiny13.menu.lto.Os_flto.compiler.c.extra_flags=-Wextra -flto -g
attiny13.menu.lto.Os_flto.compiler.c.elf.extra_flags=-w -flto -g
attiny13.menu.lto.Os_flto.compiler.cpp.extra_flags=-Wextra -flto -g
attiny13.menu.lto.Os_flto.compiler.flag_indicator=-D COMPILER_LTO
attiny13.menu.lto.Os_flto.ltoarcmd=avr-gcc-ar

When you say "library" - have you set it up as an Arduino library, or are you just throwing those source files into your sketch directory?

They are in the "libraries" subdir of my Arduino directory, as depicted in my first post.

westfw:
I looked at it some more, and I can't figure out what the problem is.
Probably all those "volatile" qualifiers prevent optimization?

BTW, "attachInterrupt()" is pretty awful WRT to code size, RAM usage, AND execution speed.

That was my first guess but even after removing the volatiles there is still some memory being used. I have detailed it in my second post. But even if they were in fact the problem I still couldn't do anything about them because I need them to be volatile.

attachInterrupt() is in the receiver code and it's supposed to be uploaded to an Arduino so I don't care about its size. I only care about the TX code size.

But it turns out that when you compile the project all cpp files get compiled, even if they are never included, which means everything global from the receiver files get copied into the ATtiny. And the tiny of course cannot handle that.

I'm not deep into this, but fighting currently a similar situation, so take make answers with care:

  • you are using the old library format (without src folder), therefore all files in the library root get compiled and linked.
  • You could put the optional files in a subfolder (NOT src), and add an additional #include in your user sketch depending, if it is needed or not.

noiasca:
I'm not deep into this, but fighting currently a similar situation, so take make answers with care:

  • you are using the old library format (without src folder), therefore all files in the library root get compiled and linked.

  • You could put the optional files in a subfolder (NOT src), and add an additional #include in your user sketch depending, if it is needed or not.

  • a user has pointed me to the entry of

dot_a_linkage=true

in the library.properties , which brings the IDE to archive all .o files before the linker will do his job and therefore the optimization should work. But I haven't found time to test this so far, but you could give it a try.

Thank you! It worked!

The problem seems to be the linker. Checking the IDE output it becomes evident that the compiler compiles everything in the library folder and the linker links all of them together. I assumed anything that was not #included would be excluded from this process but apparently they aren't.

Changing the directory structure didn't help because according to the docs everything in the "src" folder and all its subdirectories will still get compiled and linked.
And if I put the files in a folder other than the "src" folder as you suggested, then they will not get compiled and the linker gives me "undefined reference" errors. (I will try more and see if I can get this to work).
However the last method, adding the "dot_a_linkage=true" to the library.properties file did the job. Finally the dumb linker realizes that it should not link the files I'm not including.
According to the docs what "dot_a_linkage=true" does is:

First, all source files are compiled into .o files as normal. Then instead of including all .o files in the linker command directly, all .o files are saved into a .a file, which is then included in the linker command.

And "not including all .o files in the linker command directly" is exactly what I needed to happen.

I never found this documentation before. I was using the ones at arduino.cc which are apparently quite outdated?
https://www.arduino.cc/en/hacking/libraries and https://www.arduino.cc/en/Hacking/libraryTutorial. They don't even mention the new docs or the new structure. I only found the new doc because you mentioned a new structure, src folder, library.properties, etc.

Anyway, now the user has to #define if they want the RX or TX functionality before including my library and then the corresponding part of the code will get compiled.

I'm going to research a little bit more into how I can get the linker to leave my unused files alone. Since this method will only work with Arduino IDE 1.6.0+.
In any case I will post the final results here for future reference.

If anyone else knows any other ways of getting the linker to not link unincluded files please share.

nice to hear that you were able to solve the problem.
(unfortunatly I still get errors with dot_a_linkage=true therefore I modified my post, but I will give it a try...)

btw: Sparkfun has a nice "how to Write a Great Arduino Library" and some interesting deep links.

Posting my final settings for future reference:

File structure:

Arduino
├── Libraries
│ ├── TinyRF
│ │ ├── library.properties
│ │ ├── src
│ │ │ ├── TinyRF.h
│ │ │ ├── TinyRF.cpp
│ │ │ ├── TinyRF_TX.h
│ │ │ ├── TinyRF_TX.cpp
│ │ │ ├── TinyRF_RX.h
│ │ │ └── TinyRF_RX.cpp

And in library.properties you add dot_a_linkage=true.

Visit Redirecting for more information on library structure.