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\$
  • 4
    \$\begingroup\$ Why not let the main loop handle the display while button presses are handled by interrupts? \$\endgroup\$ – brhans Aug 17 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\$ – homokinetics Aug 17 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 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 at 18:40
  • \$\begingroup\$ @user253751 you mean using the internal timer to trigger a clock tick? \$\endgroup\$ – homokinetics Aug 18 at 19:02
28
\$\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.

| improve this answer | |
\$\endgroup\$
  • 5
    \$\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 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 at 13:02
  • 1
    \$\begingroup\$ @DaveTweed This particular part apparently has 224 bytes of total RAM... \$\endgroup\$ – Lundin Aug 17 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 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 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)

| improve this answer | |
\$\endgroup\$
  • 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 at 14:53
  • \$\begingroup\$ @awjlogan could you debounce in hardware by using, say, capacitors? \$\endgroup\$ – AnnoyinC Aug 17 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 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 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 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.

| improve this answer | |
\$\endgroup\$
  • \$\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 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 at 1:25
  • \$\begingroup\$ many thanks guys for the support. Will definitively try it out during this week. Really appreciate it. \$\endgroup\$ – homokinetics Aug 17 at 2:14
  • 14
    \$\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 at 6:57
  • 10
    \$\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\$ – Andrejs Gasilovs Aug 17 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).

| improve this answer | |
\$\endgroup\$
  • \$\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 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 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\$ – homokinetics Aug 19 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 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.

| improve this answer | |
\$\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.