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
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.
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
}
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.
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?
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 ?
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.
I've wondered exactly what the author(s) of the IDE meant by that.
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."
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.