4
\$\begingroup\$

I'm currently studying electrical engineering. Due to the pandemics, my classes were suspended and I'm using this time learning more about electronics and programming.

I'm currently trying to use a Pic16f628a and a generic digital display to build a digital clock with some features. The thing, is that I'd had to access a menu pressing a button in execution time, while the clock is being displayed. Normally I'd call a thread for the clock display and the main thread would be watching for inputs, but due to the simplicity of the pic controller I can't use the resource.

So, my C code (not yet implemented specifically to pic) is something like this:

void display_timer(){
  static struct current_time timer;
  static int is_time_set = 0;
  set_current_time(&timer, &is_time_set);

  while (is_time_set){
    system("clear");
    printf("########\n");
    printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
    printf("########\n");
    sleep(1);
    update_timer(&timer, &is_time_set);
  }
}
int main ()
{
  while (1){
    display_menu();

  }
}

During the sleep(), the controller would have to be able to watch for new inputs and act correspondingly.

One alternative I though was to use a state machine to store a button press, dividing the sleep function into 4 or 8 intervals, something like this:

  while (is_time_set){
    system("clear");
    printf("########\n");
    printf("%d:%d:%d\n", timer.HOURS, timer.MINUTES, timer.SECONDS);
    printf("########\n");
    for (int i = 0; i<8; i++){
    if (state_machine_input == 1){state_machine_input = 0; break;}
    sleep(1/8);
    }
    update_timer(&timer, &is_time_set);

It could be done, but I'd appreciate if I'd not have to add more complexity to the project, adding another state machine for example. What could I do in therms of software to implement this functionality?

\$\endgroup\$
6
  • 4
    \$\begingroup\$ Why not let the main loop handle the display while button presses are handled by interrupts? \$\endgroup\$
    – brhans
    Aug 17 '20 at 0:56
  • \$\begingroup\$ bhans, thanks for the answer. I'm taking it in consideration. As I answered right now, I'm used to high level programming style and still adapting to embedded systems resources. The way I thought, would be better to keep a thread ticking seconds in order to adjust the clock or the alarm, or the timer (the challenge includes theses functionalities) and update the screen. The main thread would handle other functionalities, s.a. inputs. But I'm not fully aware if that is the right way to do so. \$\endgroup\$ Aug 17 '20 at 2:12
  • 6
    \$\begingroup\$ One way is to write everything in an infinite loop. Don't sleep, ever. Just don't update the display unless it's been one second since the last time you updated the display. If you have nothing to do at all, you can enter low-power mode to wait for an interrupt. \$\endgroup\$
    – user253751
    Aug 17 '20 at 11:47
  • \$\begingroup\$ Break the bits of work into individual routines. Once a second (or whatever), check to see which bit of work needs to be done and do it. Then wait a sec. \$\endgroup\$
    – Hot Licks
    Aug 18 '20 at 18:40
  • \$\begingroup\$ @user253751 you mean using the internal timer to trigger a clock tick? \$\endgroup\$ Aug 18 '20 at 19:02
29
\$\begingroup\$

Threading is a higher level concept than microcontroller programming. Simply put, threads are implemented as a scheduler that uses timer interrupts, which in turn saves the program counter + stack pointer etc and sets those to different locations. So it is quite possible and easy to implement a similar concept using interrupts - with the benefit that you get specialized interrupts instead of generic multi-threading.

That's about the only sensible way to do it with a restricted legacy 8 bitter like PIC, which is extremely limited when it comes to stack use. Forget about using thread libs, even those written for microcontrollers. That will only add excessive bloat and complexity, for nothing gained. It is a bad idea in general to drag PC programming concepts into the embedded world.

What you should be doing, is to put your button scanning inside a cyclic timer interrupt that's executed once per 10ms or so. From inside the interrupt, you poll the buttons and compare the button read with the previous once, for debouncing purposes. The result of this is stored in a variable shared with the main program, declared as volatile and protected from race conditions. Since you only write to the variable from inside the interrupts, it may be sufficient protection to ensure that reads are 8 bits, but you must disassemble to be sure. More info about that here: Using volatile in embedded C development.

\$\endgroup\$
7
  • 6
    \$\begingroup\$ Your objection to protothreads is based on ignorance. Did you even look at it? It is cooperative, not preemptive (no scheduler, no interrupts, no stack manipulation) and very lightweight. And cooperative multi threading is a commonly used programming paradigm in embedded systems doing nontrivial mixes of functionality. Everyone I know who has been doing this kind of work for more than a few years has either designed or adopted some way of implementing it, and protothreads is just one example. \$\endgroup\$
    – Dave Tweed
    Aug 17 '20 at 11:17
  • 11
    \$\begingroup\$ @DaveTweed I have used them before in one project. The true issue is that too few embedded programmers manage race conditions correctly even with plain interrupts. Threading makes it even worse. As for light- vs heavyweight, did you even look at the PIC16 stack? They are extremely small and some versions of the core only handle 8 levels of function call depth. You can't really add any form of abstraction layer at all in such a restricted environment, the core is way too limited. Why people still insist on using 8 bitters is another story... \$\endgroup\$
    – Lundin
    Aug 17 '20 at 13:02
  • 1
    \$\begingroup\$ @DaveTweed This particular part apparently has 224 bytes of total RAM... \$\endgroup\$
    – Lundin
    Aug 17 '20 at 13:05
  • 3
    \$\begingroup\$ That isn't the "true issue" at all. The OP is already familiar with and comfortable working in a multi-threaded environment. And as I said, PT does not touch the stack at all, and it only requires an extra 2 bytes of RAM per thread -- hardly onerous, even with 224 bytes total! \$\endgroup\$
    – Dave Tweed
    Aug 17 '20 at 13:15
  • 3
    \$\begingroup\$ From an educational point of view if OP is already familiar and confortable working with higher level paradigms it would make more sense to grasp the low-level concepts of this kind of low-end microcontroller and build up from there: e.g. edge-triggered interrupts for the buttons, main loop to update the display, then move the display update in a timer interrupt, etc. All of these registers manipulations would be abstracted away on higher-end devices, and using (proto-)threads is doable on most of them anyway. It just depends on OP's goal: learning about lighweight MT or the µc architecture. \$\endgroup\$
    – strnk
    Aug 17 '20 at 15:43
15
\$\begingroup\$

Use interrupts

You want to run some code when pressing a button? Use a pin-change-interrupt

You want to do something at a fixed interval? Use a timer-interrupt

In a way, the hardware of the microcontroller runs a 'thread' that monitors the interrupt sources, and runs a 'callback' or interrupt-routine for each event.

The main program is automatically paused while executing the interrupt.

A common way to share data between interrupts and main code is through volatile global variables and temporarily disable interrupts when reading data from these globals when they are more than the word-size of the controller (almost always on an 8 bit controller)

\$\endgroup\$
9
  • 3
    \$\begingroup\$ Cautionary note on using interrupts for push button - unless you have debounced it in hardware, you are likely to get an undefined number of interrupts occuring before the button settles. For buttons, a regular sampling is better option. \$\endgroup\$
    – awjlogan
    Aug 17 '20 at 14:53
  • \$\begingroup\$ @awjlogan could you debounce in hardware by using, say, capacitors? \$\endgroup\$
    – AnnoyinC
    Aug 17 '20 at 21:56
  • \$\begingroup\$ @AnnoyinC You could, of course, but it's 1) fixed function, so you have to design for the worst case, 2) more BoM cost, and 3) why would you, given that you have a much more flexible microcontroller already there? \$\endgroup\$
    – awjlogan
    Aug 18 '20 at 9:21
  • \$\begingroup\$ @awjlogan you can still disable the interrupt after the first edge. Then, you could enable a timer-driven interrupt that will enable again the pin change interrupt after some ms. \$\endgroup\$
    – next-hack
    Aug 18 '20 at 12:12
  • 1
    \$\begingroup\$ I usually use a pin change interrupt to capture the initial switch, and wake the micro from (deep) sleep, and the use a systicks- or equivalent counter to ignore any bounces for a few ms. I like the regular sampling as well, so I'll consider it for non-wakeup inputs \$\endgroup\$
    – Pelle
    Aug 18 '20 at 17:52
11
\$\begingroup\$

I would probably suggest a cooperative multitasking library. One that I have used in the past is Protothreads: http://www.dunkels.com/adam/pt/

Any decent cooperative multitasking library will help abstract away the implicit state machine required to keep track of things.

Good luck.

\$\endgroup\$
7
  • \$\begingroup\$ I second this. It's what Bruce Land at Cornell teaches his students. I know this because his students frequently write up their projects for Circuit Cellar magazine, and they keep mentioning it. \$\endgroup\$
    – Dave Tweed
    Aug 17 '20 at 0:43
  • 1
    \$\begingroup\$ I third it. It only takes a very few moments to create a co-operative scheduler. A data structure plus an assembly routine (or in-line assembly, if preferred) called "switch" or whatever that uses a simple data structure holding stack info with the only task being to save registers that are supposed to persist over call edges, switches stacks, and then restores those registers before returning. That's about it, really. If you want over-writable thread messages just add an entry in the data structure for that, too. Queues can be added, but often aren't needed. Some init code, too. Hour or two? \$\endgroup\$
    – jonk
    Aug 17 '20 at 1:25
  • \$\begingroup\$ many thanks guys for the support. Will definitively try it out during this week. Really appreciate it. \$\endgroup\$ Aug 17 '20 at 2:14
  • 15
    \$\begingroup\$ Most likely the wrong solution to a different problem. There is absolutely no need to use thread libs for this, it just bloats the program out of proportion for nothing gained. It's a bloody PIC... \$\endgroup\$
    – Lundin
    Aug 17 '20 at 6:57
  • 11
    \$\begingroup\$ I second Lundin. Are you guys ok? Adding multithreading to a such a simple problem and for an 8-bit PIC? Next time topic stsrter would be adding separate thread for uart handler, I2C and everything else. \$\endgroup\$ Aug 17 '20 at 9:49
6
\$\begingroup\$

There are in general different approaches with multitasking when it comes to embedded system:

  • Polling or Cooperative Multitasking: Everything is done in one infinite loop and the tasks are designed to take the minimum time possible and return to the main execution as fast as possible, to avoid delay. Note that tasks suitable for this architecture might not be what you would think of in terms of higher-level concept, for example in your application one task could be update_display and another task could be check_button and you would build a loop such as:
    while(1){
         check_buttons();
         update_display();
         sleep(0.1); //seconds
     }
  • Interrupts: All possible inputs as connected to hardware interrupts and the main execution is left for things that cannot be put on interrupt (might be nothing, in which case usually the microcontroller is put in a sleep mode to reduce power consumptions. Details of how this is done usually depend on the particular microcontroller and compiler used.

  • RTOS: depending on how much power the microcontroller provides, it might be possible to run a Real-Time Operating System (RTOS), which might provide with API to create tasks or even threads. This depends on the application and the hardware capabilities, and for educational examples should not be necessary (or advisable, imo)

Consider also that another important part in deciding the overall architecture of the application is the division in tasks and how they cooperate. One of the paradigms used is the State Machines (the link is to the general wikipedia page that might be overwhelming, simpler resources specific to embedded programming can be found on your textbook or on google).

\$\endgroup\$
4
  • \$\begingroup\$ In general, that's true. But this is a very old PIC with extremely minimal resources, so it doesn't really answer the OP's question. \$\endgroup\$
    – Graham
    Aug 18 '20 at 21:25
  • 2
    \$\begingroup\$ While it doesn't directly serve a solution to OP's actual problem, it gives the context to understand where the problem comes from, the standard approaches to solve it and the terminology used to express them. My main goal was to provide readers like OP, with experience in programming but not much in embedded, with a few key elements useful to get started, since I expect this question to attract a lot of beginners to embedded programming. \$\endgroup\$
    – bracco23
    Aug 18 '20 at 21:41
  • \$\begingroup\$ thanks @bracco23 for your effort explaining these concepts. I really appreciate it. I feel kinda off that my university requests such challenges without much explaining besides what is shown on classes (some windows software, a crappy ide and a lot of pain and frustration). Digging through these technologies i found some didactic materials that used floppy disks containing compiler software past in that time. Still researching, but found very good resources on hackaday to help me kick out this project. Many thanks dude! \$\endgroup\$ Aug 19 '20 at 15:38
  • \$\begingroup\$ There are a lot of resources online, a lot of them usually focused on more commercial systems like Arduino though. Still concepts are the same, I'm sure you will be able to get it through :) \$\endgroup\$
    – bracco23
    Aug 19 '20 at 15:54
5
\$\begingroup\$

Mostly, in 8-bit devices have limited source. I think simpler solution is better solution in 8-bit PICs.

You can create hardware Timers for 2 different tasks. Set a flag and check the flag in your infinite loop, do the task and reset the flag. Don't use delays. This method guarantees to done your tasks in your infinite loop, if your flags up.

But, you have to know that the tasks is not executed exact time when the flags up. If there was two flags set at same time you can't know which one is executed first. Because you don't know where in the infinite loop. But, it's mostly okay for not time critical interfacing applications.

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

For small embedded designs I've had a lot of success using a simple task switcher, based on RIOS. It's super simple and easy to port to any platform - all you really need is an interrupt giving you a timer tick. If you split the workload of your system into a set of state machines, with each one running as a single task, you can achieve quite complex systems with no need to use an RTOS. Each task can use static variables to preserve local state.

Basically it's a poor man's RTOS without the overhead and complexity. Every task must complete within one timer tick, so it's absolutely predictable in timing.

There is a bonus which is that this gives you a pretty decent understanding of what most OS's are doing underneath.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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