0
\$\begingroup\$

I am learning to program microcontrollers (with PIC18FXXXX) and what I have seen so far is that everything that is programmed within the main function is what is executed.

My question is, if you can make a call to a function that is outside the main and is running a scheduled task.

For example. Press a switch and call a function void turn_on_led () and it activates an output of the microcontroller.

But it is not possible to do this.
What alternatives are there to do these things?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Why do you say this isn't possible? Nearly every example of Microcontroller programming shows at least one function call... \$\endgroup\$
    – Ron Beyer
    Commented Mar 22, 2020 at 19:37
  • 1
    \$\begingroup\$ Can you clarify what exactly is your question? A) only functions that are invoked (directly or indirectly) from main() ever get executed, or B) all code is in main(), there are no functions (or C) something else entirely) \$\endgroup\$
    – anrieff
    Commented Mar 22, 2020 at 19:40
  • \$\begingroup\$ I mean multitasking.Do several tasks in parallel. \$\endgroup\$
    – Geo
    Commented Mar 22, 2020 at 19:49
  • 1
    \$\begingroup\$ Geo, the multitasking part is essential and changes your question dramatically. Please add it to your question! Also how to make a CPU do multitasking is a little too big to answer in this forum. (Which is about electronics and not programming). \$\endgroup\$
    – Oldfart
    Commented Mar 22, 2020 at 20:09
  • 1
    \$\begingroup\$ en.wikipedia.org/wiki/Computer_multitasking \$\endgroup\$ Commented Mar 23, 2020 at 16:20

4 Answers 4

3
\$\begingroup\$

Multitasking?

Yes or no, depending on how you define it.

In the truest spirit of multiprocessing:

No.

A small single-core MCU only executes a single instruction at any given instant.

In the spirit of the 90's multitasking:

(when almost all PCs had only a single CPU, but you still had OSes with multitasking):

Yes.

You can emulate it, by switching very fast between execution contexts.

How can you pull it off on a very tiny MCU?

The CPU capabilities are quite limited, but it's not impossible.

Consider the following piece of multithreaded code:

void task_A(void)
{
    // multiplex a 7-segment display. Show the number "42" on its two digits, by alternating "4 " and " 2".
    while (1) {
        CATHODES = 0b00;
        ANODES   = 0b01100110; // '4'
        CATHODES = 0b01;       // illuminate only the first digit
        __delay_us(500);       // wait 0.5ms

        CATHODES = 0b00;
        ANODES   = 0b01011011; // '2'
        CATHODES = 0b10;       // illuminate only the second digit
        __delay_us(500);       // wait 0.5ms
    }
}

void task_B(void)
{
    // do some other business, like wait for keypresses, or compute something
    compute_the_meaning_of_life_universe_and_everything();
}


void main(void)
{
    launch_task(task_A);
    launch_task(task_B);
}

You cannot run this straight to a PIC MCU; there's no way to launch these two tasks in parallel. What you can do, instead, is to configure a timer interrupt to fire off every 500µs, and cause a state transition in the two states you have in task_A, like this:

void setup_PIC(void)
{
    // setup a timer to cause an interrupt every 500µs...
}

int whichDigit = 0;

void interrupt ISR(void)
{
    // this handler gets called (automatically) each 500µs (or as you've configured it)
    whichDigit = !whichDigit;
    if (whichDigit) {
        CATHODES = 0b00;
        ANODES   = 0b01100110; // '4'
        CATHODES = 0b01;       // illuminate only the first digit
        // note there's no __delay_us() here!
    } else {
        CATHODES = 0b00;
        ANODES   = 0b01011011; // '2'
        CATHODES = 0b10;       // illuminate only the second digit
    }
}


void task_B(void)
{
    // do some other business, like wait for keypresses, or compute something
    compute_the_meaning_of_life_universe_and_everything();
}


void main(void)
{
    setup_PIC();
    task_B();
}

Now this is a very reasonable way to have a display that's driven independently of the main code (task_B()).

You can even do it without using interrupts (and there are several reasons why you want to avoid doing complicated things in an interrupt handler), by directly calling ISR() every-so-often from task_B(). This is akin to cooperative multitasking.

Use your peripherals!

A lot of MCUs come in packed with hardware peripherals inside, which you can use to do things "in parallel" of sorts. E.g. your PWM peripheral will toggle the state of an output pin automatically, without direct supervision from the main task. Or the analog opamp present in some chips can amplify a signal, this works independently from the main code as well. These can well give you the impression that the chip is doing several things at once, because it really is - those peripherals require configuration only, and they work independently afterwards.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the answer, it has helped me understand the concepts that were not clear. The examples are fine, but if I really want to multiprocess what alternatives are there or what can PIC do? \$\endgroup\$
    – Geo
    Commented Mar 24, 2020 at 11:02
  • 1
    \$\begingroup\$ The 8-bit PICs would likely be too weak to do real multiprocessing; something simple as the example is about the best they can do, and you have to organize it yourself. If you want transparent multitasking, I'd suggest you pick up a beefy enough MCU that can run an OS (e.g. FreeRTOS). Some popular chips nowadays (ESP32) are even dual core, they do 100% real multiprocessing, and they run on FreeRTOS as well; sadly one of the cores is tied up with system stuff (WiFi/TCP stack), but if you insist you can rewrite that part and do true parallel computations (I'd recommend against it though). \$\endgroup\$
    – anrieff
    Commented Mar 24, 2020 at 13:23
  • 1
    \$\begingroup\$ What I can recommend is that you learn FreeRTOS and set up tasks, and experiment with multi-threading as you wish. Just not try doing it on a 8-bit PIC, it's going to be more pain than fun. If you want to stick with Microchip, you can use PIC32 or PIC24 (full list here). \$\endgroup\$
    – anrieff
    Commented Mar 24, 2020 at 13:27
3
\$\begingroup\$

Why do you think it is not possible? Functions can be called and not using functions leads to undecipherable spaghetti code. There are both professional quality designs but also beginner hobbyist code so you can find both. Some extremely tiny microcontrollers can have a limited hardware stack so subroutines are not used much, even if it is possible.

\$\endgroup\$
4
  • \$\begingroup\$ So far everything i have seen in the pic18fxxx microcontrollers, only everything called inside the main is executed. I have not seen that you can make calls to functions that are declared outside the main. \$\endgroup\$
    – Geo
    Commented Mar 22, 2020 at 19:23
  • 1
    \$\begingroup\$ Well now that you mention what MCU you are talking about does change things but only slightly. Regardless, functions can be called. The PIC18F series just has some models with extremely limited hardware stack which may allow only two nested calls. And there must be room left for interrupt call as well. So while possible, calling subroutines can be a very scarce resource with some extremely tiny microcontrollers. \$\endgroup\$
    – Justme
    Commented Mar 22, 2020 at 19:37
  • 2
    \$\begingroup\$ @Justme PIC18F compilers found a way to cheat. They will assign function arguments to a specific address in memory. It is a good workaround, but functions cannot be called recursively nor are they thread-safe. \$\endgroup\$
    – Ben
    Commented Mar 22, 2020 at 19:45
  • 2
    \$\begingroup\$ The question changed from being a question about subroutines to question about multitasking. Sure, there are many levels of multitasking. Like timer interrupts. Or just round-robin between all tasks. Or make them as coroutines. \$\endgroup\$
    – Justme
    Commented Mar 22, 2020 at 21:16
3
\$\begingroup\$

To put it simply, Yes.

There may be some conceptual issues with your understanding now. Or maybe your language used to describe your problem. I will attempt to answer anyway.

The "main" function is the one being executed by the micro's main execution core. Everything that is executed normally will be called from the main function. The main function can call other functions as necessary. However, there is still one sequential order of execution. So yes the main function can indeed call functions that are declared elsewhere.

There are also other things called "interrupts". Most micros will allow you to create a function that is executed by an interrupt when a certain hardware event happens. You can have an interrupt handler for an IO port so that when you press a button the main program is halted while the interrupt handler is executed.

A different way to do this would be to have a function inside of the main function that keeps checking to see if the button has been pressed and once it decides that it has, executes that same "button pressed" code.

\$\endgroup\$
1
\$\begingroup\$

My question is, if you can make a call to a function that is outside the main and is running a scheduled task.

First and foremost, it is impossible to concurrently execute multiple threads of execution on an 8-bit microcontroller that has a single core. At any given time only one thread of execution can be running on the microcontroller's one core.

Second, if you do not provide an operating system (OS) then there is no OS, and therefore there is no task scheduler, no application programming interface (API) that provides OS services, etc. In this case the executable image that's stored in the microcontroller's flash memory must be a mash-up of the desired program logic, hardware device drivers, asynchronous event handlers (interrupt service routines), etc. (NB: This is typical when creating software for 8-bit microcontrollers.)

For example. Press a switch and call a function "void turn_on_led ()" and it activates an output of the microcontroller. But it is not possible to do this.

This is possible, and in fact it's done all the time. The specifics depend upon (a) the specific microcontroller you are using, and (b) the software development tools you are using.

Most microcontrollers have digital input pins that can be configured (by your program) to detect a specific signal condition at a digital input pin—e.g., a low-to-high logic state transition—and when that signal condition occurs the microcontroller pauses the presently running code and calls a function (an interrupt service routine, ISR) that responds in some desired way (whatever your program dictates) to that signal condition. This is known as asynchronous event handling.

For example, your program might configure the microcontroller so that when a low-to-high logic transition occurs on digital input pin 2, the microcontroller pauses the running program and then invokes a function named 'foo()'. Function foo() might assign the value 'true' to a global variable "button_pressed", which informs the main program code that a button press event (a low-to-high logic transition) was detected at input pin 2. Function foo() then returns, and then the microcontroller resumes execution of the previously running program code. At some point the main program checks the state of the global variable "button_pressed" and sees its value is now 'true', and the main program (a) responds to that button press (however the program dictates), and (b) assigns the value 'false' to "button_pressed" so that a subsequent button press can be detected.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.