This is an appeal for some guidance to help me understand the way the code (my code) will be executed, so that I can design it properly. This is not a request for the code, I'll learn and do that myself, some guidance is all I ask.
Firstly, my task. This is the Mk2 version of a working code, but the desire to add some extra functionality (such as DS18B20 temperature sensors and an I2C display) means I am taking the opportunity to re-write the software from scratch - and learn some new skills in the process.
Over a two second period I will fire a solid state relay for a proportion of that two second period. To put in context that is 100 cycles of mains power @50Hz, so a possible need to change the output within 20ms of the start of this two second period. I have been reading Robin Emley's excellent article on building his Mk2 Energy Diverter (www.mk2pvrouter.co.uk), and in it he explains that the ATMega328 processor is using about 25% of its time running the programmed tasks, and that's when he's measuring the current at an analogue input about 50 times per 20ms mains cycle! He concludes that it can be given other work to do in its free time. That's great, and I assume in my related project using an Arduino Uno that I will have some spare processing time too.
My specific problem is how to make sure those extra tasks don't affect the fundamental timing of the output switching.
Forgive the pseudo-code, but as I am only at the design stage for Mk2 it seems pointless to post the Mk1 code.
Primary functions (must be done on time):
Read current from current transformer CT
Calculate the output value (Max current available minus CT value)
Switch on output for the required time, then switch it off.
Supplementary functions (to be added):
Read temperature sensors (only once per 5 seconds or so)
Read temperature setting potentiometer (once per 2 seconds or so)
Calculate information for display
Output to display (I2C) (update once per 2 seconds or so)
My specific question is, I suppose:-
A function may take longer than 20ms to complete, how do I delay that function call so that it's done in the processor's idle time, for example if the output is 20% I'll have 400ms to play with DURING the output ON time and 1600ms AFTER it's switched to OFF. If the output is 1% I'll have only 20ms whilst ON, but a massive 1980ms after the OFF command. Simlarly the times are reversed in the upper output periods.
How can I make the 'calling' of these supplemental functions flexible so that they don't interfere with the important switching?
Break the functions up so that no remaining function takes longer than say 15ms to run, you may need more than one of these functions to complete your task. Each time round the loop process the next function bering in mind that a high priority function such as dealing with the timing wll need to always be processed if required. An ISR should do the minmal amount of work possible such as record a value and set a flag, then during the loop the flag can be checked and a function called to process the recorded value from the ISR. The time round the loop has be to short enough to ensure that the ISR is not called twice thereby missing one of your values.
There are several ways to go, depending on your skill level and criticality of the code. You can start with several things at the same time to see how to structure your code so it "seems" like you are doing many things. This is usually the easiest.
The second way would be to set of an interrupt to do your power reading/calcs and then just deal with them in your main loop while doing your secondary things.
if your "idle" time can be in the ON or OFF stage, you can always set a variable when doing your power calculations. Then, in your loop(), if the "idle" variable is true, you do your secondary work, if it is not, you don't.
blh64:
if your "idle" time can be in the ON or OFF stage, you can always set a variable when doing your power calculations. Then, in your loop(), if the "idle" variable is true, you do your secondary work, if it is not, you don't.
Brilliant, and (I think) just what I need.
So I can code in the following (if I understand correctly):
Find the zero crossing point for the mains electricity (from Robin Emley's work) and call it 'Z0'
Variable A = millis() at time Z0
Variable B ='output time in ms' + A
(Determine the durations of each supplementary function (or sub-function) in ms)
If B > time for function 'f1' then do this function
if B > time for function 'f2' then do this function
...and etc for all supplementary functions.
I'll have to set a flag to discount the 'done' functions until the next time they're due, and another to hold over 'not done' functions so they're tried again after the output goes low again, but the pseudo-logic seems to work.
Follow the several things at a time tutorial already mentioned.
I would think that you should be aiming for a function to complete in 1ms or less, and if it takes more than 5ms then I'd be thinking about ways to speed it up. Put everything into separate functions and make them a slick as possible. The only thing in loop() should be calls to other functions. If there is something critical that can't wait for loop() to go round once then put those calls in multiple times interleaved with the ones that can wait.
The basic structure is to call a function, immediately do a test to see if its task needs doing right now, if it is, do it, if not return and try the next one. Never, ever, ever EVER wait for anything, either it needs doing now or it doesn't.
++Karma; // I think this is one of the most important things you can learn on your journey from beginner to experienced programmer.
Perry, thanks for that tip.
If I understand that correctly I should:
Call a supplementary function
Within that function do the 'if there's enough time then do this, else return' test
Call the next supplementary function, etc, etc..
Keep loop() as short and as fast as possible so that it runs several times within my 20ms window of opportunity.
The parts I cannot control are, for instance, the DallasTemperature external library, and here I show my ignorance. When I ask for the temperature sensor(s) to be read/converted it can take around 750ms for a 12 bit resolution, and 180ms for 10 bit resolution. The smallest window of opportunity within my 2 second cycle is when the output is 50%. On the surface it appears that I will have to carefully stagger the reading of multiple DS18B20 sensors so that my switch timings are not delayed during the 10 bit readings (only two sensors will require 10 bit / 0.5 degree resolution). Is this correct?
The standard library for DS18B20 sensors allows you to read them asynchronously. By default it waits the 750mS period but you need not. If timing is critical for you, there's obviously no point waiting.
Look at the WaitForConversion2 example that comes with the Dallas One Wire library. It shows you how to do asynchronous readings. Start the reading, note the time, go off and do other things, and when time has elapsed, read in the results.
Within that function do the 'if there's enough time then do this, else return' test
Call the next supplementary function, etc, etc..
Keep loop() as short and as fast as possible so that it runs several times within my 20ms window of opportunity.
For 2. the test is whatever it needs to be for the function. If the function has to do its thing once per second then the test might be to see if millis() has increased by at least 1000 since the last time it did its thing. If the function is to update a display then the test might be to see if there is anything to update (no point writing to a display if nothing has changed).
As to the specific example you gave, probably not. If your code has timing critical things in it that matter within milliseconds then you need to know how long your functions take and make sure they are all written to be shorter than whatever the critical time is. However, don't let me influence you on this, you decide what tests you need.
As you have observed some libraries trip you up. You've got the answer to that one but you need to be wary of this. You might end up writing your own code to speed things up. Any library that does something that waits for a reply or response is a problem and will have to be investigated. For me I tried using client / server using TCP and the library I used waits up to several seconds (!) for a response from the server, this is useless. I didn't dig in to it to see how to fix it as I wasn't that bothered about pursuing it, but it gives an idea of what you have to be careful of.
If you look at the sample code in any of my tutorials you will see it is written with these principals in mind.
Glorymill:
2. Within that function do the 'if there's enough time then do this, else return' test
To add to what @PerryBebbington said, there is no way to know whether there is "enough time" - that's trying to forecast the future.
Just make sure what happens in a function takes as short a time as possible and only does stuff that is essential. Break long running tasks into several shorter steps.
A couple of weeks have gone by, and I'm ashamed to say I have done very little except pontificate, and procrastinate.
To recap, I'm controlling a heating element with an SSR (solid state relay) using burst firing over a 2 second period, ie every 2 seconds I read the input current transformer value, calculate the output time that the SSR will fire for (between 0 and 2000ms), and send that output to an Arduino pin. This has been achieved. Now to add the bells and whistles.
My next challenge is to add further devices to the Arduino, such as an LCD display, several temperature sensors, a connection to a website for setting parameters by mobile phone, and the like. This means more demands on the processor's time, but this must not be at the cost of significant loss of SSR timing accuracy. I am trying to design a new strategy that will allow for 'scaling-up' of these new features and more, and fit them into the timing available.
The excellent suggestions so far have lead me to the following strategy, one that I hope will allow the system to grow and add features in the future without disrupting the core SSR driving function.
Keep loop() short.
Bring in those extra features as a series of short functions, of short duration.
A single transgression of the SSR timing rule will not cause significant disruption to the overall project goal.
Some extra functions should be run more often than others (they have a higher priority), but all functions need to be run eventually. Eventually could be once per 10 seconds, and the value of 'eventually' will be determined and modified by me from experience of using the system.
Setup() may become extensive, and this will not be detrimental to the overall project. It will only add a delay on powering-up.
I have to guard against any one function 'hanging-up'. I suppose a kind of watchdog timer.
Here come the questions!
I want to use setup() to list all the functions in order of the time needed to execute them, and here my ignorance shows:
a) What is a sensible way to find the average time to run each of my functions during setup(), and list them in order of execution time ready for use by the loop() function?
b) Is the above simply a case of running each 10 times and dividing time taken by 10?
c) Will each function be processed in a consistent time each time, so no need to average a number of runs? I have in mind @PerryBebbington 's tip to 'test if the input has changed before you run the function'...if I break early then the function will complete more quickly, and move lower down the batting order. Hmmm, perhaps setting initial variable values will allow the full function to run during setup() and give it a more representative position in the list; if it completes more quickly in loop() that is no problem.
d) Does the variable name have any bearing on the run time? ie will int BloodyLongVariableName57 take longer than int x ?
(I think no because the compiler will give it a simple memory location, but prudent to check with you).
e) If I were doing the above with a series of numbers I would use an array, and compare the array elements to each other in turn to sort them in largest to smallest order. Is that same technique suitable for sorting these function times in order?
f) How do I get the program to call the functions in the order determined at setup()? I think this is the most difficult concept for me to master.
g) To add further complication (and I only add complication where it is necessary) I will need to set a flag at the completion of each function, so that the remaining functions only are run during subsequent loop() iterations. The strategy for this, incorporating my 'priorities', will be to decrement a counter value within the function each time it is called, and NOT run it until that counter reaches zero. Thus functions with lower initial counter values will be run more often than others (ie ReadCurrentFromCT will have a count value of 0 and be read on every occasion, UpdateLCD will have a value of 10 and only be executed every 10 calls from loop(). Do you foresee problems with this approach?
h) looking at point 6 above, I suppose I should avoid while... or similar statements. Any other traps for the unwary?
My apologies for this post being so long, and whilst I note that this forum is for Programming questions I don't expect anyone to write any code for me except for a snippet or two to demonstrate a suggestion.
I would not bother with such a complicated approach until forced to. Add your new components and use millis to tell you how long they take. The only one I would expect to be potentially slow would be the web connection.
I doubt you'll need to measure temperature all that frequently. Similarly, there's no point refreshing the LCD too fast or you won't be able to read it.
So at most (to start with) I'd do the SSR stuff and one other thing if it was time for it on each iteration of loop. In reality, I wouldn't even implement that restriction until my telemetry told me I had to.
I want to use setup() to list all the functions in order of the time needed to execute them
I agree with @wildbill. I would not (I never do) bother with any of that complexity. Just create the functions you need and call them in a logical order - X may need to happen before Y - or if their actions are independent just call them in any order.
Write your code so that the functions don't waste time and only worry about how long things take if an actual problem emerges.
If you write your code carefully only doing things when they need doing and making sure there is no use of delay () or while loops then none of what you are thinking about will be necessary.
Stop thinking and start writing code.
If you post what you have done we may well be able to suggest improvements.
Bring in those extra features as a series of short functions, of short duration.
A single transgression of the SSR timing rule will not cause significant disruption to the overall project goal.
Some extra functions should be run more often than others (they have a higher priority), but all functions need to be run eventually. Eventually could be once per 10 seconds, and the value of 'eventually' will be determined and modified by me from experience of using the system.
Setup() may become extensive, and this will not be detrimental to the overall project. It will only add a delay on powering-up.
I have to guard against any one function 'hanging-up'. I suppose a kind of watchdog timer.
Correct.
Correct, but the real killers are delay() and while, avoid delay() completely, use while with a great deal of care.
You are way off on this, the timing scales are such that you don't need to worry about this. With heaters the relevant time scales are from a few seconds to many minutes or even hours. A few milliseconds or a few cycles of mains either way is utterly irrelevant.
You would have to write a humongous setup() function or include the most ridiculous amount of delay() for you to even notice that setup() was taking any time at all. However, if you have a lot of setup code it makes sense for tidiness to create functions for each thing you want to set up. It will make no difference to the code, but should be easier to read.
If they are properly written they won't hang up. If they do hang up you need to find out why and fix it.
PerryBebbington:
Stop thinking and start writing code.
This. It's good to plan, but paralysis by analysis isn't. And remember, Arduino type hardware is cheap: if you really do run into performance issues on your Uno (which I doubt) you can just buy your way out of trouble.
wildbill:
The standard library for DS18B20 sensors allows you to read them asynchronously. By default it waits the 750mS period but you need not. If timing is critical for you, there's obviously no point waiting.
This is the task I am currently reading up on, or rather trying to read up on, and having difficulty. The DallasTemperature Control library seems to be the place to start, and all roads seem to lead to GitHub, where the only parts of the library that appear to have any descriptions of the functions and procedures within are DallasTemperature.h and .cpp, and these don't appear to have much to help the novice.
For instance, within the .cpp file is a function that may be useful in my project, yet there is little to explain further.
Is this all I get, and am I stuck because I am trying to use a library beyond my capabilities?
// TRUE : function requestTemperature() etc returns when conversion is ready
// FALSE: function requestTemperature() etc returns immediately (USE WITH CARE!!)
// (1) programmer has to check if the needed delay has passed
// (2) but the application can do meaningful things in that time
Command all DS18B20s to perform a temperature conversion
Go off and do something else.
Return after the highest resolution conversion has completed (>750ms) and allocate each temperature to a variable that can be used within my program.
(I'd also like to master other Dallas commands, just not right now!)
Please will you point me towards some further reading, or the route to better understanding the Dallas library.
I can't suggest further reading but I strongly suggest trying the code and the different functions to see how they behave and learn how to use them that way.
Some years ago I wrote some Arduino code that was supposed to use a DS18B20 to send temperature data from a Greenhouse to a server. It worked for years, though now a Pi does the job. I've just taken the bit that reads the sensor asynchronously and prints the data. It compiles but I didn't test it. Might be useful:
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 6
OneWire ds(ONE_WIRE_BUS);
DallasTemperature sensors(&ds);
const int LedPin = 5; // Led that shows prog is still running
const int WaitForDS18B20=750; // Number of mS to wait for Sensor readings to be ready after the async call to request them has been made.
float RelativeHumidity; // Sensor sometimes shows spurious peaks, smooth out changes & keep running humidity here
float SensorTemp; // Temperature in F. Global so we can initialize it in setup.
void setup(void)
{
Serial.begin(9600);
pinMode(LedPin, OUTPUT);
sensors.begin();
sensors.setResolution(12);
SensorTemp=sensors.getTempFByIndex(0); // Intialize to ensure we don't send Zero to the web server. Not async mode yet, so no need to worry about managing the delay
sensors.setWaitForConversion(false); // makes temp reading async, have to manage the delay yourself
}
void ReadSensors()
{
static unsigned long TimeOfRequest; // As we're using Async, need to know when the DS18B20 was asked to get temps
static bool WaitingForDS18B20=false; // Are we waiting for the DS18B20 to get temp data ready for us to read?
if(WaitingForDS18B20)
{
if(millis()-TimeOfRequest > WaitForDS18B20) // Have we waited long enough for the DS18B20 to write temp data on its scratchpad?
{
SensorTemp=sensors.getTempFByIndex(0); // Apparently so, let's get it
WaitingForDS18B20=false; // Set to trigger a new request next time this routine is called
}
}
else
{
sensors.requestTemperatures(); // Ask DS18B20s to start reading temps
WaitingForDS18B20=true; // Set so we don't ask again until we've read temp
TimeOfRequest=millis(); // Need to know when we asked, so we can wait the requisite delay
}
}
void loop()
{
static unsigned long LastSensorRead; // We're dealing with temp & humidity in a greenhouse. No need to sample continuously
static unsigned long LastLedFlip; // Control the led flash. Just there to tell me it hasn't crashed
static unsigned long LastUpdateTime; // Last send to the web server
static bool LedOn=false;
if(millis()-LastSensorRead > 1000UL)
{
ReadSensors();
LastSensorRead=millis();
}
if(millis()-LastUpdateTime >= 90000UL) // Check if it's time to send an update to the web server - 90 second intervals
{
Serial.print("Temperaure:");
Serial.println(SensorTemp);
LastUpdateTime = millis();
}
if(millis()-LastLedFlip > 250UL)
{
LedOn = ! LedOn; // Flash the LED so I know when the Arduino has crashed irretrievably and can perform a manual reset
LastLedFlip=millis();
digitalWrite(LedPin,LedOn);
}
}