Dear Arduino forum,
I'd be very grateful for some general advice about how to advance my current Arduino project - hopefully this will stimulate some debate about good working methods. I am using a Mega 2560 to control a number of simultaneous processes as follows:
Temperature Sensing and Control
Once every 500ms, Arduino takes readings from an array of thermocouples.
If any of the thermocouples show a new reading (above a certain threshold), this value is then linearised according to a set of coefficients.
The thermocouple readings are then passed to a PID controller which uses this data to maintain a desired temperature.
The temperature readings and PID controller results are passed to a GUI (processing) over serial.
Motor Control
An analog signal is read continuously and this value is used to control the speed of a stepper motor.
Receiving Commands Over Serial
A serialEvent function listens for commands sent over serial - the GUI (processing) has some simple functions for adjusting the PID controller.
As this project evolves I hope to add more functions.
I have measured the amount of time it takes for the 'Temperature Sensing and Control’ part of the sketch to execute. This is typically between 200-400 ms, depending on how many new temperature values are read. This length of time is quite problematic because during this period the analog signal that controls the stepper motors is not read and no signals are sent to drive the stepper motor. As a last resort, I am considering whether it would be better to have a second Arduino that is dedicated to controlling the motor but I would much prefer to have one Arduino manage all of the functions. I would be very grateful for any suggestions as to how i can manage these simultaneous functions. Am I correct in my assumptions that this micro controller is not able to send pulses to a motor whilst doing other things?
I’ll share my code (both Arduino and Processing) as hopefully those looking to implement PID control will benefit from this.
My current project is similar, though more complex. I have multiple DC servo motors, controlled by PIDs, several other simpler devices, as well as multiple UIs implemented across serial devices, telnet, and a web server. Each device is controlled by its own state machine, and all the state machines communicate with each other, so they can coordinate their actions appropriately. The basic architecture is like this:
ALL UI activity occurs in the foreground, via calls to the various UI handlers from loop(). Each of these handlers is itself a state machine. Since these UI handlers methods are called from the foreground, they do not occur on any particular schedule, but do occur more than often enough for all the various UIs to ALWAYS remain very responsive.
ALL PIDs, and motion control state machine update methods are invoked by a scheduler running on a 1mSec timer intererupt. Each PID has a pre-set update interval (2mSec for the high prioirity ones, 10mSec for the low priority ones. All state machines update at 10 mSec intervals. The scheduler determines which PID updates and state machine updates occur on any given imSec clock "tick".
Various interrupt inputs, primarily for the many sensors.
Communications between the many state machines is via message queues using a simple request/response protocol, so any part of the system can "talk" to any other part of the system at any time, and is guaranteed to get always a response to any message. This infrastructure also provides a very simple means of pushing out messages from ANY par of the code, including interrupt handlers, through dedicated queues, that provide debug message output, as well as detailed trace information from all the various state machines. This debug and trace output is the primary debugging mechanism, since the application cannot be paused for debugging without losing control of all the motion controllers. I can always "see" exactly what sequence of states each state machine executes, as well as any internal data necessary to understand what events occurred, when they occurred, what triggers each state transition, and the timing of events across and between all the state machines.
Basically, you want to put all high-priority processing into update methods called by the scheduler, and leave everything else to foreground tasks which are executed as processing time is available. Contrary to what is often stated on this forum, it is NOT at all necessary to keep interrupt handlers to just a few lines of simple code - the bulk of the work is done in an interrupt context, including all of the floating point PID processing. As long as the processing done in the interrupt handlers never consumes ALL of the time between 1mSec interrupts, the foreground tasks will remain responsive, assuming you NEVER use delay(). In my case, the roughly the CPU is running interrupt handlers 70-80% of the time on average, yet the many UIs are always very responsive. I am running on a Due, which is a very nice platform for this kind of application, due to its high clock rate (the ARM is ~6-10X faster than AVR-based Arduinos), and generous memory (I'm currently using ~200K of FLASH, and ~32K of RAM).
@RayLivingston That's a very nice description and example of scheduling. Apart from the OP's request, I'd like to ask what you utilize to trigger your 1ms interrupts.
Are you using an external timer/oscillator?
Also, have you had an application so far that you have had to increase your deadline interval to more than 1ms, say like 2 or 3?
arthurprior:
I have measured the amount of time it takes for the 'Temperature Sensing and Control’ part of the sketch to execute. This is typically between 200-400 ms, depending on how many new temperature values are read.
Then don't do all of 'Temperature Sensing and Control’ in a single pass through loop().
Divide the total task into steps and keep a variable to serve as index for what step to run from one pass through loop() to the next. You can reasonably expect to get 10K to 50+K executions of loop() per second if you don't write blocking code such as delay() into your loop(). I have a 4 button and 1 led demo that gets over 57600 cycles through loop(). I inserted delay(1) into loop() and the speed went to 980 cycles. The 16000 cycles that delay wastes totally overbalanced the appx 278 cycles everything else took to do real things. So don't use blocking code like delay and have fast, smooth tasking.
Have you done the BlinkWithoutDelay example sketch contained in your IDE?
ApexM0Eng: @RayLivingston That's a very nice description and example of scheduling. Apart from the OP's request, I'd like to ask what you utilize to trigger your 1ms interrupts.
Are you using an external timer/oscillator?
Also, have you had an application so far that you have had to increase your deadline interval to more than 1ms, say like 2 or 3?
An external timer? Why, when the MCU always has several built-in timers? On AVRs, I typically use timer 2. On the Due, there are 8 timers to choose from.
I meant, 1 ms seems like your interval time for a hard deadline where all tasks need to be finished by that point, and you had .2ms free to do all high-level/low priority things.
Using your method, have you had applications where even 1ms isn't long enough to complete all tasks, and had to allot more time. Such that the lowest priority operations still had time to resolve.
ApexM0Eng:
Using your method, have you had applications where even 1ms isn't long enough to complete all tasks, and had to allot more time. Such that the lowest priority operations still had time to resolve.
No. The most time-consuming operation is the PID updates, and I often have to do two within a 1mSec window. Never had a problem. If some operation occasionally runs a little over 1mSec, it only means the next interrupt will be delayed by that same amount. As long as the next one doesn't consume too much time, it'll end up back in sync on the following interrupt, with no harm done. I used to use 2mSec interrupts way back in the stone age with 2Mhz 8080 processors. Even a lowly AVR-based Arduino is blindingly fast compared to those. Few users seem to appreciate how much you can get done on these little processors, with good code. I can hardly think of a system I've worked on where processor bandwidth was a limiting factor. If bandwidth, or responsiveness, are issues, you're almost certainly doing something very wrong.
Cool. I haven't been able to start any projects yet that have a span big enough to warren't scheduling. But I am looking forward to getting more home space and supplies to get to that point.
ApexM0Eng:
Cool. I haven't been able to start any projects yet that have a span big enough to warren't scheduling. But I am looking forward to getting more home space and supplies to get to that point.
You do know how to do timing with unsigned longs?
if ( millis() - startMillis >= waitMillis )
{
// time to make the donuts
startMillis += waitMillis;
}
And to read serial as it comes in?
void loop()
{
if ( Serial.available())
{
Serial.write( Serial.read()); // could be buffering the reads or evaluating the data as it arrives.
}
}
I have a nice technique to remove delays from functions. It involves putting a timer at the front of the function and splitting the function into pieces that end at every delay and the end code as cases in a switch-case with a state variable as the switch. Instead of delay, the case sets the start and wait times, sets the state to next and breaks. The timer code is set inside if (waitMillis > 0 ) so as to only run when desired (wait > 0). The timer returns without running the switch-case if the wait is not up.
I was able to fix code for devices I didn't understand using that technique because the code as was worked, it just didn't work well with anything else. Once I fixed that for two devices, it all ran well... sort of but someone should unblock the GSM library, begin() consistently got us over 30 second lockups.
Another possibility to structure your code is to use a multitasking scheme as used by Arduino-MOS.
By means of the MOS macros you can remove all delays from your functions and structure the code into several tasks with different priorities. And this without additional consumption of RAM and Flash resources.