Arduino sketch experiancesPosted by Jan Tue, March 24, 2015 13:55:32
If you have not yet read the blog post Controlling Arduino over the web: Blinking a led the cool way. Part 1&2; please read them first.
What you will learn in this post:
In the previous post we add my blinkled library to make a led blink based on serial input.
The point of this post is to change the blinking rate using the serial in a generic way. In other words we want to get rid of the string parsing in the loop() method of the previous example.
I consider this a steep slope learning curve to understand what is going on. To use it; it is a lot easier.
Here is a link to the sample program this article is about.
Lets get started:
Even after two introductory blogs I still find it hard to find the best entry to explain how I do it. So lets start with what you probably think is the most interesting: "What do I need to do to get it to work."
From a conceptual point of view: What are the steps needed to automate changes to variables via the serial port?
1) A list of the variables you allow access to including the access type.(further on called list)
2) Code to register variables to the list. (further on called serialRegister)
3) A component to control the serial port that uses the list to communicate with the serial port and changes values in Arduino memory. (further on called SerialCommunicator)
The list: conceptually
The list has to contain all the information the SerialCommunicator needs to get his job done.
It needs a pointer to the variable so it can change/read it.
It needs a name so it can be referenced to via the serial communicator.
It needs access control flags so we can have read only objects and read/write objects.
It needs the type of the object so we can convert the text to the correct memory content to write to the pointer.
The DUMP serial command actually dumps the list and the memory values the list points to. Lets look at the partial dump of Marvin my mowrobot for a nice example.
Marvin has a temperature sensitive resistor in a voltage bridge connected to a ADC pin of the Arduino. Arduino calculates the Celsius as follows
Lets go into the dumped fields:
The name is a concatenation of names. I map my class instantiations on the name (it is not necessary to do so). In other words I have a class instantiation RobotSensors which holds a member called mow_temp which holds a member Left which holds the members Celsius, ActualreadValue and MultiplyerValue.
modflag is a collection of binary orred values. The first bit means write the second bit means save (more on save in a later post)
The ActualReadValue is the value arduino returns from the analogRead method. Therefore I do not allow to change the value and modflag is 0.
type: I store the value in a unsigned int so type is uint16_t
value: is 0 as the last analogread before the dump returned O.
Because MultiplyerValue and Offset have the write attribute you can change the value of those while Arduino is running to tune the value of Celcius on the fly -over the web-. Isn't that cool?
Registering is adding a value to the list. You should register all your fields in the setup() routine. For each field you have to register the name, the mod flag, the type and a pointer to the variable.
The serial communicator needs to take control over the serial port. This means you can no longer use the serial port as an input device as the Serial communicator is already reading the serial port.
You can however write to it because the SerialCommunicator only writes in his loop() method and will not exit the loop() when the write is unfinished.
Further on the serial communicator must be capable to convert memory variables to a textual representation and the other way around.
The serial communicator must be able to search the list to find the field to modify.
The serial communicator must be capable to report error conditions and feedback actions.
The serial monitor must guard that the read/write rules are respected.
This is a whole lot. But the good news is... The serialCommunicator is a library you can download.
Now we know what the big parts are lets see how to implement them.
I implemented the list as an array. (apart from array I did lots of thinking, considerations and implementations which are out of scope of this article) This means you have to declare an array and assign a size to it. The size of the array is a bit of a pain-point as you have to make it big enough to get all fields into it but you only know how many fields you have when you have compiled and run the code.
This is why the list is actually visible 2 times in the sample program. Once to declare the variable and once to check the size
#define MAXFIELDS 70
//As MAXFIELDS needs to be big enough it is not a bad idea to know how big it is now
SerialOutput.print(F("Current memory fields "));
SerialOutput.println(lastFieldIndex); //yes I do use global variables
SerialOutput.println(F(" from "));
SerialOutput.println(MAXFIELDS); //13 of 70 that is way more than enough
As long as you have enough fields in the list you can forget about it.
note: Until now I only talked about field names. However in the code I make a distinction between the class and the field. Field is everything after the last dot (commonly called leave) and class is everything before the last dot. -see the F macro why I did so-
The methods to register a field in the list are called set and setnext. The difference between the 2 is that setnext assumes the class parameter is the same as the previous call to set.
Lets look at the serialregister of led13
void BlinkLedSerial::serialRegister(const __FlashStringHelper* Name)
You see I register 5 fields for the led.
I'm using C++ overloading to get the correct type from the pointer. This makes the code far more reliable.
As I use globally instantiated classes and I wrap all the set(Next) calls of a class in the serialRegister method; I mostly only need to call serialRegister for each globally defined class instantiation in setup() to create the whole list.
Just like in the sample program
I implemented the SerialCommunicator but that does not mean there is nothing to do for you.
One thing I like to know about my sketches running on a arduino is: What sketch is running? Therefore the dump command starts as follows:
Dumping all fields
CompileDate Aug 16 2013
The code can find out the compile date but the SketchName is something you need to provide. Here is how:
const char mySketchName PROGMEM="Serial Communicator step3";
The second thing to know is that the SerialCommunicator has a serial register that is probably very interesting to register. I (nearly) always register him first.
This is what the loop looks like in this example
// The loop function is called in an endless loop
// yes!!!! yes!!! That is all we need in the loop to change the blinking of the led
Really, nothing more is needed. Isn't this easy to maintain?
Compile the sample, upload and play with it using the serial monitor.
What is that with the F macro and PROGMEM..?
(or about strings and memory usage)
As you are probably aware the Arduino has very little memory. The architecture of the AVR also makes that "memory" is not one big thing but it exists out of "program memory" and "real memory".
The "real memory" is what you are likely to run out first. Using char arrays increases that process. Using compiler directives you can direct the compiler to only use "program memory" this means you need special routines to read the char arrays. You can not write to program memory char arrays.
Arduino implemented the F macro (as far as I know: no pun intended) and __FlashStringHelper to help arduino programmers out. The PROGMEM is a gcc fix.
I'm using this technique to save memory but -due to the no write rule- it has some drawbacks: like the artificial set and setnext and classname and fieldname. The reason is that you can not add 2 PROGMEM/F char arrays and put them in a PROGMEM/F char array.
|Choose site to share content on|