General advice on programming Arduino

This is the first time I use a microcontroller, thanks for good advice last time.

I have had some minor obstacles writing my first Arduino program. I just wonder if there are any general advice concerning the differences between programming micro controllers and other programming. I encounter programming situations I have not seen before. Are there any general quick fixes to try on these.

I had some unexpected behavior. But removing structures and just storing in simple variables helped.

Another help was to completely remove arrays from the code. The arrays were small and I now use simple variables. Index was probably not outside bounds.

Should I avoid arrays and structures?

I had one println statement of a fixed string that could not be changed. If I added or removed just 1 char the program flow changed.

I finally cut away a lot of println and program worked much better.

I only send single characters as commands. Sometimes I have to send a command several times to have it accepted.

All I/O is done over Bluetooth.

Millis() function gives long int. I only subtract them from eachother. Can I still get problems here?

Now the program fills about 25% of the available memory.

Is there any general advice to make programming smooth?

I understand it is difficult to solve this without seeing the code but are there any general pitfalls to avoid?

So why not post it here, using code tags when you do. Which Arduino board do you have ?

The Arduino is programmed in C++ with extensions that allow you to access the chip hardware. That means that structures and arrays are fully supported within the memory constraints of the chip being used. There is no need to avoid using them

You say that millis() gibes you a long int. That is not strictly true because it returns an unsigned long int. Using subtraction with the timing variables enables you to avoid any problem with rollover to zero

That is often a symptom of blocking code which prevents input being read in a timely manner. When we see your code we can give more advice

So why don't you show it here?

Using structs and arrays are definitely no general pitfalls ( but array index out of bounds definitely is ). And writing blocking code is often a problem.

Who said so? That was definitely not helpful.

@MicroBahner @UKHeliBob
Thanks for advice. I must structure the code a little before I put it here. I will definitely read up on blocking code.

I use Arduino Uno avr. The most standard board I believe.

Yes, unsigned long is what I use for millis() of course.

Your replies have already pointed me in a direction to look for the unexpected behavior.

No one told me to remove arrays. Bad choice of wording on my side.

Suggestions of things you need to master

  • Always start a project with a circuit schematic showing all the components and their interconnections.

  • Code “must” be written in a non blocking fashion.

  • To keep code organized and easy to follow, the State Machine technique needs to be adhered to.

  • Using HIGH or LOW does not explain what is happening, use names like: LEDon, RELAYon, ENABLED, CLOSED etc.

  • Avoid magic numbers. Use names that describe the value.


unsigned long oneSecond = 1000; // 1000 ms in one second

unsigned long oneMinute = 60 * oneSecond;

unsigned long oneHour = 60 * oneMinute;

unsigned long oneDay = 24 * oneHour;

  • Let the compiler do the math for you, for 15 minutes, instead of 900000

use: 15ul * oneMinute

  • Keep version backups so you can always go back to the last working version.

  • Do not use delay(…) as it will stop code execution during this delay interval.

  • Avoid using while( ) if there is any chance it significantly blocks normal code execution.

  • Use non blocking TIMERs based on the millis() or micros() functions.

  • Write your code in a neat easy to read style, use white space, add meaningful comments throughout.

  • Use the Arduino onboard LED on pin 13 as a heartbeat LED to show code is running and code is not stuttering.

  • For mechanical switches avoid looking at digital input levels, instead, look for digital input “changes in state”.

  • For readability, place { and } on separate lines by themselves, place each line of code on a separate line.

  • LEDs displaying the current state in a State Machine, can be used to diagnose problems.

  • For diagnosing problems, use serial monitor print statements to confirm variables are what you think they are.

  • Write only small blocks of code at a time. Prove the new code block works 100% before proceeding.

  • The way you learn how to write software is by writing software.




Make this commitment to yourself.

  • Before wiring and turning on the power to a circuit, I will always draw a schematic showing how I will connect the components in my project; this schematic is exactly how I will wire things.
  • I will double check that power connections are correct.
  • I will always use a series resistor of 220Ω on both Inputs and Outputs to protect my controller’s GPIO pins from my blunders.
  • I will never re-wire a circuit when power is applied.

if by other programs you mean those run on laptops that perform some fixed amount of processing, for example, sort a file, then one difference, not because it's a micro, but that it's repeatedly doing something on some hardware.

In this sense an arduino is an engine, repeatedly monitoring input resulring in some output.

see below an example of a command processor that processes single letter command and handles multi digit values used by those commands. It has some commands allowing you to reconfigure and read/write pins, avoiding recompilation

// pcRead - debugging using serial monitor

const char version [] = "PcRead 240209a";

int debug = 0;

// -----------------------------------------------------------------------------
// process single character commands from the PC
#define MAX_CHAR  10
char s [MAX_CHAR] = {};

int  analogPin = 0;

void
pcRead (void)
{

    static int  val = 0;

    if (Serial.available()) {
        int c = Serial.read ();

        switch (c)  {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            val = c - '0' + (10 * val);
            break;

        case 'A':
            analogPin = val;
            Serial.print   ("analogPin = ");
            Serial.println (val);
            val = 0;
            break;

        case 'D':
            debug ^= 1;
            break;

        case 'I':
            pinMode (val, INPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT");
            val = 0;
            break;

        case 'O':
            pinMode (val, OUTPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" OUTPUT");
            val = 0;
            break;

        case 'P':
            pinMode (val, INPUT_PULLUP);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT_PULLUP");
            val = 0;
            break;


        case 'a':
            Serial.print   ("analogRead: ");
            Serial.println (analogRead (val));
            val = 0;
            break;

        case 'c':
            digitalWrite (val, LOW);
            Serial.print   ("digitalWrite: LOW  ");
            Serial.println (val);
            val = 0;
            break;

        case 'p':
#if !defined(ARDUINO_ARCH_ESP32)
            analogWrite (analogPin, val);
            Serial.print   ("analogWrite: pin ");
            Serial.print   (analogPin);
            Serial.print   (", ");
            Serial.println (val);
            val = 0;
#endif
            break;

        case 'r':
            Serial.print   ("digitalRead: pin ");
            Serial.print   (val);
            Serial.print   (", ");
            Serial.println (digitalRead (val));
            val = 0;
            break;

        case 's':
            digitalWrite (val, HIGH);
            Serial.print   ("digitalWrite: HIGH ");
            Serial.println (val);
            val = 0;
            break;

        case 't':
            Serial.print   ("pinToggle ");
            Serial.println (val);
            digitalWrite (val, ! digitalRead (val));
            val = 0;
            break;

        case 'v':
            Serial.print ("\nversion: ");
            Serial.println (version);
            break;

        case '\n':          // ignore
            break;

        case '?':
            Serial.println ("\npcRead:\n");
            Serial.println ("    [0-9] append to #");
            Serial.println ("  # A - set analog pin #");
            Serial.println ("  # D - set debug to #");
            Serial.println ("  # I - set pin # to INPUT");
            Serial.println ("  # O - set pin # to OUTPUT");
            Serial.println ("  # P - set pin # to INPUT_PULLUP");
            Serial.println ("  # a - analogRead (pin #)");
            Serial.println ("  # c - digitalWrite (pin #, LOW)");
            Serial.println ("  # p - analogWrite (analogPin, #)");
            Serial.println ("  # r - digitalRead (pin #)");
            Serial.println ("  # s  - digitalWrite (pin #, HIGH)");
            Serial.println ("    t  - toggle pin output");
            Serial.println ("    v  - print version");
            Serial.println ("    ?  - list of commands");
            break;

        default:
            Serial.print ("unknown char ");
            Serial.println (c,HEX);
            break;
        }
    }
}

// -----------------------------------------------------------------------------
void
loop (void)
{
    pcRead ();
}

// -----------------------------------------------------------------------------
void
setup (void)
{
    Serial.begin(9600);

    Serial.println (version);
#if defined(ARDUINO_ARCH_ESP32)
    Serial.println ("esp32");
#endif
}
  1. Always keep in mind that you may run out of memory.

  2. Arduino does not support try-catch run-time error handling. Any exceptions thrown will cause the controller to reset.

==> Code should preferably be written in a non-blocking manner.

That is very often a symptom that you are writing to memory outside the bounds of an array, or some other memory that should not be written to, such as by using a pointer incorrectly, or using a pointer to a variable that no longer exists.

Pay attention to the fact that array indexes start at 0, not 1.

Code should be written in a non blocking manner where this is important to its correct operation

25% of program memory, or 25% of dynamic memory (ram)?
Note that the dynamic memory shown by the compiler does not include local variable, or memory allocated at run-time.

If I am asked to write code to read a string from the Serial Monitor until a newline character is detected, I can implement it correctly using either a non-blocking approach or the blocking function readBytesUntil(). What factors should guide the choice between a non-blocking and a blocking approach?

I am often confused when I read the term "dynamic memory." Is not it actually the "static RAM" within the ATmega328P microcontroller?

As I said before

If code is required to be non blocking then write it that way. If not then write it in the simplest way

An extreme example would be writing code to read a string until a newline character is received where this is in the setup() function and is prompting for a password. Until the correct password is entered the rest of the code is not allowed to run so why write it as non blocking ?

As opposed to memory that doesn't go away when powered down.

This is a solid example where blocking code is necessary.

I use the term because that is what is shown by the Arduino IDE to refer to the ram. Since I do not know the technical knowledge of most people who simply state "xx% memory" I want to use a term they will see when doing a compile.

But if you want to implement a time limit and countdown for entering the password then you would write it as non blocking code

Horses for courses

I've wondered exactly what the author(s) of the IDE meant by that.

  1. Maybe they are emphasizing the fact that only the amount of RAM that the compiler can know about at compile time is reported and that the amount used at run time can vary and should be considered "dynamic."
  2. Maybe the author(s) are not particularly hardware savvy and they are from the era when PCs predominately used DRAM and they are referring to that as "dynamic." Even though RAM in the early AVR Arduinos is SRAM.

We'd have to ask them, I reckon.

Added:
What I meant earlier about it referring to "memory that doesn't go away when powered down" is kind of associated with 1) above in the sense of not nonvolatile memory like program storage memory.

Now I understand that the term "dynamic memory" should be interpreted in its proper context.

Thank you for taking the time to think through the issue and present something that has truly helped me.