Coding structure and guidance

Status
Not open for further replies.

josechow

Well-known member
Hey all,

So I have a very interconnected system that tosses information back and forth between two teensy 3.2's, an arduino nano, and several peripherals. Is there an easy way to manage the chaos of managing three separate programs that will need to be uploaded to three separate devices?

I am not super code savvy, so I wanted to ask before I embarked on this journey. I was thinking that there has to be an easy way to do this so that I can keep call and response code next to each other to lessen the strain on my eyes and making sure I have the right sketch open all the time.
 
Well that is not a very detailed description of your system, but it contains two very important points: you have several, non-identical MCUs which run different applications.

First of all, all that information tossing will need some kind of definition of the interface between all those MCUs. You could start by lining that out (which hardware interface is used, what is the data format, etc.) and putting transmitter and receiver code into a library that can be used by both the transmitting and receiving device. As we are in the arduino world, you should be able to do this in a way that is independent from the actual hardware. This way you have transmitter and receiver code side by side in that library.

As far as uploading goes, there is a command line interface to the teensy loader, and there are command line (upload) tools for the arduino nano as well. Put all calls into a script (depends on your development machine's OS) to make it a bit more convenient.

Please post more details, so we can give better advice.
 
You can also use #define statements to conditionally compile code depending on your platform. E.g. if you put the complex code that won't run on a Nano between these defines it won't be compiled if the target is a Nano, but it will be compiled if the target is (the specific) Teensy. That way you only have to maintain one sketch.

E.g. in your header
Code:
#if defined(__arm__) && defined(CORE_TEENSY)
#if defined(__MK20DX128__)
#define PROCESSOR_TEENSY_3_0	1

#elif defined(__MK20DX256__)
#define PROCESSOR_TEENSY_3_1	1

#elif defined(__MKL26Z64__)
#define PROCESSOR_TEENSY_LC	1

#else
#define PROCESSOR_AVR
#endif

And then in your code:
Code:
#if defined (PROCESSOR_TEENSY_3_1)
/*Do something only the Teensy can*/
#endif
 
As Epyon mentions, you can use #ifdef's to have separate code for different processors. However, the Arduino layer makes some things harder. It is problematical to have different include files based on the processor:

Code:
#if PROCESSOR_TEENSY_3_1
#include <i2c_t3.h>

#elif PROCESSOR_ATTINY85
#include <TinyWireM.h>

#else
#include <Wire.h>
#endif

This is due to the Arduino layer trying to be user friendly, in that it collects all of the libraries mentioned in the build directory, to build everything. However, at least in the past, it did not understand #ifdef's, and it would give an error when it couldn't find say i2c_t3.h on an AVR platform, even though due to the #ifdef, the library wasn't used.

The way to do this is to break your libraries into the lower level machine independent parts, and then have machine dependent libraries that include the machine independent libraries. Within the machine independent library, you do not want to use short, int, or long types, but instead use size types that are appropriate to your problem (i.e. signed types int8_t, int16_t, int32_t, or unsigned types uint8_t, uint16_t, or uint32_t). In particular, int is 16-bits on AVR systems, and 32-bits on ARM systems.

In addition note that on AVR systems double is the same size and representation as float on the Teensy. Unless you need the excess precision on Teensy, you should just use float everywhere. You should use the float version of the math libraries (i.e. sinf instead of sin, cosf instead of cos, etc.). It would also be a good habit to add a 'f' suffix to all floating point constants.

It is generally better to transmit data via text strings, rather than binary, since it avoids problems of size or endianess (most/all processors using Arduino libraries are little endian, but it is better if you can side step the issue of how bytes are laid out within a word by going through a text layer. In addition, writing binary to some serial setups can result in data loss due to low level details that are different (whether software flow control is used, whether AT commands are interpreted inline, whether tabs are converted to spaces, whether trailing spaces are preserved, what newline characters are sent, etc.).

When transmitting data, include a version number in the data. Bump this version number whenever the basic structure changes in any way, and don't handle the wrong version.

If possible in debugging messages, print the date the system was compiled and a version number. You can use the pre-defined macro __DATE__ and __TIME__ that are replaced with character strings giving the date and time that the last compilation was done.

One other thing, start practicing backups. Make backups at least every time you make an intermediate result that works. It is best if you use a source code control system, that would allow you to unwind changes, so you can get back to last Tuesday's version. I tend to use cvs, because I am an old dinosaur, and it was the defato system when I started. Github appears to be the system of choice today. It doesn't matter which system you use, just that you use a system.

Edit:
Be wary of tests that say if it is not a Teensy it must be an AVR. Have an error in case you get a new processor:

Code:
#if PROCESSOR_TEENSY_3_1
  // ...

#elif PROCESSOR_AVR
  // ...

#else
#error "Unknown processor"
#endif

Also note, the tests for PROCESSOR_TEENSY_3_1 above will fail on a Teensy 3.5 or Teensy 3.6 which has different defines. The Teensy 3.5 defines __MK64FX512__, and the Teensy 3.6 defined __MK66FX1M0__.
 
Last edited:
Other defines are
- TEENSYDUINO for all Teensy
- KINETISK for Teensy 3.0 .. 3.6
- KINETISL for Teensy LC

or, the mentioned __arm__ for all ARM-CPUs

..and there are more defines... some, like __arm__, are defined by the compiler.
 
Please post more details, so we can give better advice.

I should have phrased it better. But Epyon and Michael got what I was getting at. It was more of an approach kind of thing. I have never learned or was taught how to organize or manage multi-platform code under one roof (or sketch in this instance). I am just starting to get into managing backups, version history, etc. I wasn't a software guy in college, so they didn't teach me that stuff!

Below works after a quick test, but is it the sane thing to do?

Code:
//#define PROCESSOR_NANO 1
#define PROCESSOR_TEENSY_1

#if defined (PROCESSOR_NANO) 
	// includes
	// variables
#endif

#if defined (PROCESSOR_TEENSY_1)
	// includes
	// variables
#endif

void setup () {

  #if defined(PROCESSOR_NANO)
    //Setup
  #endif

  #if defined (PROCESSOR_TEENSY_1)
    //Setup
  #endif
  
}


void loop() {

  #if defined (PROCESSOR_NANO)
    //only nano stuff
  #endif

  #if defined (PROCESSOR_TEENSY_1)
    //only teensy stuff
  #endif

}

void some_shared_method_nomenclature(int howmany) {
  
  #if defined (PROCESSOR_NANO)
    //only nano stuff
  #endif

  #if defined (PROCESSOR_TEENSY_1)
    //only teensy stuff
  #endif
}
 
Last edited:
Status
Not open for further replies.
Back
Top