Hi,
As a practise project, I am trying to program a PID controller on an Arduino nano with a ATmega328P with the following architecture:
Timer/Counter 0 manages the PWM output
Timer/Counter 1 works as pulse counter for the encoder
Timer/Counter 2 works as scheduler, using the internal clock to trigger the PID process (read counter, calculate PID correction value and set PWM) and the communication process
Additionally, there are other communication modules:
SPI to communicate the ATmega328p with other devices
UART for debug only
SPI, UART, PWM and pulse counter has been relatively easy to implement, but the scheduler requires a deeper knowledge and I am getting issues here.
The goal of the scheduler of to trigger two cyclical tasks, one each 10 ms to process the PID and another each 100 ms to read the target value and report the current value through SPI to the master device.
My current approach is to use Timer/Counter 2 in Clear Timer on Compare Match (CTC) mode with a compare value A and prescaler properly set to 10 ms and then set the Timer/Counter2 Compare Match A interruption to trigger the Task1 and update a cycle counter for the Task2. Task2 will be triggered by Timer/Counter2 Compare Match B when the cycle counter reach 10 (compare value B works as a shift time of ~ 5ms to avoid overlapping between task 1 and task 2)
The time diagram looks like this:
10 ms 10 ms 10 ms
|____________________|____________________|______ ... _____|__________________
5 ms | | | 5 ms
|__________| | | |__________|
|~~~~~| |=====| |~~~~~| |~~~~~| |~~~~~| |=====|
Task1 Task2 Task1 Task1 Task1 Task2
cycle = 1 cycle = 2 cycle = 3 ... cycle = 10
Those are my source code files (header definitions and register configuration is omitted for the sake of simplicity):
#include "schd.h"
#include "schd_cfg.h"
void (*task_10_ms_ptr)(void);
void (*task_100_ms_ptr)(void);
volatile uint8_t task_10_ms_flag = 0;
volatile uint8_t task_100_ms_flag = 0;
/* Configure the TImer/Counter2 as scheduler */
void schd_init(void) {
// Set the timer configuration registers:
...;
// Initialise the tasks to an empty value:
task_10_ms(schd_empty_task);
task_100_ms(schd_empty_task);
// Enable Global Interrupts:
sei();
}
/**
* Specifies the 10 ms task
* @param[in] task - Pointer to the function that will be executed
*/
void task_10_ms(void (*task)(void)) {
task_10_ms_ptr = task;
}
/**
* Specifies the 100 ms task
* @param[in] task - Pointer to the function that will be executed
*/
void task_100_ms(void (*task)(void)) {
task_100_ms_ptr = task;
}
/**
* Checks the tasks flags and execute them if the flag is true
*/
void scheduler(void) {
if(task_10_ms_flag) {
(*task_10_ms_ptr)();
task_10_ms_flag = 0;
}
if(task_100_ms_flag) {
(*task_100_ms_ptr)();
task_100_ms_flag = 0;
}
}
ISR(TIMER2_COMPA_vect) {
// Update the cycle counter
cycles++;
// Set the task flag to true
task_10_ms_flag = 1;
}
ISR(TIMER2_COMPB_vect) {
switch(cycles)
{
case 1:
// Set the task flag to true
task_100_ms_flag = 1;
break;
case (SCHD_TASK_B_TIME/SCHD_TASK_A_TIME):
// Reset the cycle counter
cycles = 0;
break;
default:
break;
}
}
void schd_empty_task(void) {
}
And this is a minimal test I wrote to test the library:
#include "schd.h"
#include "uart.h"
void task_1(void);
void task_2(void);
char str_task_1[] = "-----1\n";
char str_task_2[] = "+++++2\n";
int main(void){
// Initialise the scheduler and the UART:
schd_init();
uart_init();
// Set the cyclical task:
task_10_ms(&task_1);
task_100_ms(&task_2);
// Check the scheduler flags and execute the tasks:
while(1){
scheduler();
}
return 0;
}
/**
* Dummy task that simply prints a message through the UART
*/
void task_1(void) {
uint8_t idx = 0;
while(str_task_1[idx] != '\n'){
uart_tx(str_task_1[idx]);
idx++;
}
uart_tx('\n');
}
/**
* Dummy task that simply prints a message through the UART
*/
void task_2(void) {
uint8_t idx = 0;
while(str_task_2[idx] != '\n'){
uart_tx(str_task_2[idx]);
idx++;
}
uart_tx('\n');
}
The example works, but there are some problems:
I would like to get rid of the scheduler() function. Ideally, I want to trigger the tasks directly from the interruption instead of setting a flag, but leaving the task execution out of the interruption itself. Is that possible?
Since this approach requires a very accurate control of the time, what is the best way to get the clock cycles required by a function? My idea is to set some kind of post build test that analyses the task and verifies that there is no timing problems
It seems to be some performance issues. If I increase the length of str_task_1[] or str_task_2[] the task2 message starts to appear every 8 task1 messages or less (instead of 10), which means that the current tasks requires more than 5 ms.
Even without a clock cycle analysis those functions should be simply enough to be executed without any problem (UART uses a baud rate of 9600) so in my opinion there is a leak somewhere.
Nope!
Consider how the interrupt works. Interrupts are disabled inside of INT routine.
Of course, you can do a simple task inside of INT, this is not a problem. However in the moment you will enable interrupts, and you have to, the MCU starts performing the next instruction from the main task.
In addition, your tasks are working with uart, which is using interrupt. Interrupt inside of interrupt will cause hang of MCU.
Probably the best approach is to write this piece of code in the assembler, if you are not CPP guru event though it is pretty hard to control the cpp compiler. Definitely assembler.
If you mean precisely that one runs ten times for every time the other runs once, why not just run the slow thing every tenth time you run the fast thing?
As the diagram show, task2 has to run every ten runs of task1, but after 5ms of task1 begin.
The easiest way to accomplish this is using both compare interrupts (COMPA and COMPB). COMPA is your 10 ms tickrate (as you already have planned), and COMPB at the 5 ms mark. This way, COMPA and COMPB interrupts are always triggered, and you are always guaranteed that COMPB is always 5ms from the start of each cycle.
if the dot is 1ms, it would look something like this
Hello Alberto_Tejada
Do you have a requirement to use an interrupt.
Do you can use the Arduinio function millis() or micros() either?
Have a nice day and enjoy coding in C++.
Both the micros() and millis() functions are well structured, if you are using the Arduino IDE, there is little reason to change them, or not to use them.
Consider having multiple state machines in an interrupt that are executed in a sequential way, and use a compare if necessary.
Of course, that is a totally valid approach. Nevertheless, I want to build this project totally from scratch, without using built-in Arduino libraries.
I know that this is reinventing the wheel, but I think this is a good exercise to understand the low level programming principles and face the 'correct questions' of embedded development, which are applicable beyond the Arduino boards.
I'm not sure I understand your point. In the example, the slow task is actually running every tenth time the fast task is executed, but in addition, it waits a shift time of 5 ms to allow the task1 to be completed.
That way, if for X reason task1 requires more time, task2 is not affected (assuming that task1 will never take 5 ms even in the worst case). So task1 and task2 are more independent on time from each other.
Hello @paulpaulson, @johnwasser and @agm1dr ,
First of all, thanks for your comments. As I commented to @Paul_B, this project is developed from scratch, so I can not use millis() or micros().
Regarding the need of determinism, the problem is that the 'real' purpose of the 10ms task is to read the increment of pulses from the encoder and calculate the speed, so it is critical to guarantee that the pulses will the checked with a consistent cyclicity.
Also, consider that even if in this example the shortest interval is 1/2 of task1 cyclic time, that can be adjusted depending on the needs of task1 and task2.
If for example during the development I realise that task2 takes twice as long as task 1, I want to to be able of change the shift time from 5ms to 3ms so that there is no risk of overlapping.
The use of "millis()" for timing is the basis of a pragmatic approach to timing. It is not some random and arbitrary creation of the Arduino project, it is a universal method of scheduling in a cooperative multi-tasking program.
It is not a matter of "can not". You do not need multiple timers and interrupts in "spaghetti" code. You simply use only one interrupt to generate your own "millis()" and you organise your timing on that.