Handling tasks in parallel with PIC16 - Page 1
Forum section: electronics / general

author: newtekuser
author_rank: Frequent Contributor
timestamp: September 17, 2024, 03:31:21 am
content: I have a project using a single PIC16 MCU that interacts with external devices (stepper, rtc) as well as driving a 16x2 LCD for the UI. Menu navigation is handled using interrupts and currently, the stepper is firing whenever an alarm is hit - all of this is polled in the main loop by checking the alarm flag in the rtc periodically.While this works it is not ideal and I’d like to handle the stepper controls and rtc using interrupts or timers but I can’t figure out how while maintaining the ISR small and not crash the device.Do I need to use a second MCU and “delegate” the stepper/rtc control to it while the main MCU is handling the UI or can this be done with just one PIC?My issue is that even if I have an interrupt triggered by the rtc in the ISR when the alarm triggers I still need to call the code that moves the stepper. That is too much to have in the ISR and the device hangs/crashes.

author: forrestc
author_rank: Unknown
timestamp: September 17, 2024, 03:37:49 am
content: I didn't hear anything that should be hard to do.How are you writing the code for the PIC16? Is it in XC8?Which PIC16? The suggestions of how to handle this is going to depend on the PIC and whether it's new enough to have the newer core independent peripherals and the like.How are you moving the steppers? Do you have a driver that you give step and direction signals to or are you actually controlling the coils directly?What type of motion are you doing with the steppers? Are we talking full-on motion control (CNC, 3d Printer, Robot) or something else?

author: Andy Chee
author_rank: Super Contributor
timestamp: September 17, 2024, 03:46:25 am
content: I would adjust your ISR periods so that the stepper gets more ISR time than the LCD, human input. The latter items surely don't need to be checked/updated nearly as frequently as a stepper motor?The term is task scheduling.

author: newtekuser
author_rank: Frequent Contributor
timestamp: September 17, 2024, 05:19:53 am
content: The device is based of a PIC16F887. Compiler is XC8. The motor driver is a DRV8825 and the code for moving the stepper that get's called within the ISR is pretty simple, just a few lines but it's taking too many cycles as the stepper needs to move thousand of steps.As far as I know the PIC16F887 does not support interrupt priorities and I can't move to another PIC. That's why I was asking if maybe my only option is to add another PIC, something cheap and tiny like the PIC16F18013T to handle the stepper separately from the rest.

author: Andy Chee
author_rank: Super Contributor
timestamp: September 17, 2024, 05:59:08 am
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amMy first thought is;is it necessary to wait for the stepper cycle to complete its entire movement, all within the ISR period? It seems to defeat the purpose of using an ISR in the first place!My first thought is that the ISR can initiate the stepper movement, then resume main body code, then the ISR can periodically check whether the stepper movement has completed, upon which a flag "movement complete" can be set. Then your other routines can check whether this flag is set.Alternatively, the ISR should only ever execute one stepper "step" at a time, NOT the complete stepper movement. Re-entering the ISR at regular (and frequent!) intervals will increment the stepper position step by step.

author: fchk
author_rank: Frequent Contributor
timestamp: September 17, 2024, 06:03:53 am
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amWhy can't you? The 3-digit models are rather old, and there are pin-compatible successors like PIC16F18877 with enhanced features.Or you can go with thishttps://www.microchip.com/en-us/product/dspic33ev256gm102(28 pins, there are also 48, 64, 80, 100 pin versions). This is MUCH more powerful, runs on 5V, and you can keep your IDE and programmer.

author: brucehoult
author_rank: Super Contributor
timestamp: September 17, 2024, 06:12:27 am
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amI'm not seeing where the issue is. That's just a simple stepper driver, as you find on the very common Pololu, for small stepper motors. Nothing high performance or industrial etc.You need to pulse one pin for min 1.9 µs a maximum of maybe 10,000 times a second (usually much less, even with microstepping).The PIC16F887 runs at 8 MHz (2 MIPS). It should be I think about 8 cycles overhead to enter and exit the ISR, a couple of instructions to set the "step" GPIO, 5 or 6 NOPs for timing, a couple of instructions to clear the GPIO, return from the ISR. That's maybe 6 µs total if you've got the interrupt regularly triggered from a timer. A few more µs if you need to set up the timer again.And you need to do this masimum once every 100 µs, but more likely every ms or longer.It seems to me you should only be spending 10% of the time in the ISR, absolute maximum, but probably a lot less.

author: forrestc
author_rank: Unknown
timestamp: September 17, 2024, 07:52:55 am
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amThere are numerous processors which are pin and electrical compatibility with the PIC16f887, so unless there's a non-technical reason for not being able to move, you should be able to just drop almost any of the modern 8 bit pics in that footprint without any board changes. A very fast, new, modern with lots of memory example is a PIC18f26Q84. Depending on your application, you may find that you can offload most of the work to the core independent peripherals depending on the processor. But that probably won't be necessary.Regardless, I'd rearchitect as follows:1) Move the stepper code to the ISR, move everything else into your main loop. The stepper is the only thing that you mentioned which is time-critical enough that it needs to be in an interrupt.2) reduce the ISR code to be just "toggle pin, update count/compare/ toggle pin, return" (Assuming you're stepping once per timer tick, and only doing a certain number of steps at once). I.E. do the bare minimum in the interrupt.3) Be aware that things like pushbuttons can still use the 'interrupt flags' as event latches without an interrupt. For instance, if you've got something that you're relying on an interrupt to fire to catch, you can easily just look at the interrupt flag in the main loop to determine if an interrupt would have fired and then do that.4) The main loop should still be very very fast, like thousands of times per second, unless you're doing a LOT of things in the loop.

author: voltsandjolts
author_rank: Unknown
timestamp: September 17, 2024, 07:57:06 am
content: Can the OP post the ISR code please?

author: woofy
author_rank: Frequent Contributor
timestamp: September 17, 2024, 09:19:08 am
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amWhat is the reason you cannot move to another PIC?I am baffled that you are able to make such a huge hardware change (to remove the drive to the DRV8825, add a communication link to the PIC16F18013 and connect that to the DRV8825) yet cannot use a better chip in the first place.

author: woofy
author_rank: Frequent Contributor
timestamp: September 17, 2024, 09:34:49 am
content: The DRV8825 has selectable micro-stepping from full to 1/32.You don't need to do micro-stepping at high speed, so reduce down to full step as the speed increases without overloading the PIC16F887.

author: MarkF
author_rank: Super Contributor
timestamp: September 17, 2024, 09:55:17 am
content: Let me give you some pseudo code of how I handle user inputs from a rotary encoder.You should be able to do something similar by incrementing a count of the number of RTC interrupts. Then the main loop would update the stepper motor with the number of counts received and redraw the display. It won't be one-for-one with the interrupt but should be fast enough.If the time required to update the display is long, you may actually need to breakup the display_drawing into smaller parts. Treat drawing the display as a background process. Its update rate is slow compared to everything else.Code:// GLOBAL VARIABLESint en0=0; // encoder countint sw0=0; // encoder switch pushed// MAIN ROUTINEvoid main(void){int cnt;// TODO: Add initialization code// Main Loopwhile (1){// Process encoder switch pushedif (sw0 != 0) {sw0 = 0; // reset switch state from ISR routine// TODO: Add code for switch pushed}// Process encoder turnedif (en0 != 0) {cnt = en0; // save encoder counten0 = 0; // reset encoder count from ISR routine// TODO: Add ccode for encoder turned}// Draw displaydrawDisplay();// TODO: Add any other required periodic processing// Main loop delay (say approx. 100Hz update)// This rate is not time criticaldelay_ms(10);}}// ISR ROUTINEvoid __interrupt() isr(void){// Read encoder pinsif (encoder has been turned) {if (clockwise) {en0++;}elseif (counterclockwise) {en0--;}}// Read encoder switchif (encoder switch released) {sw0 = 1;}}// DRAW DISPLAY ROUTINEvoid drawDisplay(void){// TODO: add code to update the display}

author: NorthGuy
author_rank: Super Contributor
timestamp: September 17, 2024, 01:35:25 pm
content: Quote from: newtekuser on September 17, 2024, 05:19:53 amI don't think this is a problem.But anyway, depending on how your stepper moves, you can try to use periphery to drive it. For example, PWM may be used if you want to move your stepper in one direction for a given number of steps. UART or MSSP modules can be used to transmit a number of pulses to the motor at a time. Since you don't say how your stepper moves, it's hard to suggest anything more specific.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: September 17, 2024, 02:00:47 pm
content: One of the most important things when learning to program microcontrollers is to not use software delay functions.You probably have a lot of long _delay() functions for your stepper motor timing. Such is typical "arduino" programming for beginners. It's easy to get started, and blink a few LED's, but it won't get you very far.Another very important thing is that ISR functions should be very short. Typically 100instructions, and they should execute in less then a micro second. The reason for this is that while an ISR is active, your microcontroller can not do anything else. (Yes, I know, the peripherals keep running on their own, and it is now becoming more common that microcontrollers have an ISR controller with multiple ISR priority levels).Translated to you stepper motor problem, a good way to do it is to dedicate a timer peripheral to your stepper motor(s). When the timer runs out, it triggers and ISR, the ISR doesA single stepand then programs the timer with a new delay for when to do the next step, and it returns. You can have a look at the open source GRBL hal project for how they do it. GRBL forks have come together again in it's own HAL (Hardware Abstraction Layer) project, and it supports many different uC families.And the more you can put in main routines, and the shorter you can keep your ISR's, the more performance you can squeeze out of the microcontroller you use. A long time ago (25+ years, before GRBL existed) I wrote my own stepper motor driver library. and it worked like this:A PC breaks down G code into little sections (arcs' into lines, lines into accelleration, cruse and decelleration sections).PC sends these short commands to a microcontroller, which puts them in a circular buffer.The micro controller's main program breaks these commands down into timing parameters for individual stepper motors, and puts these as commands in another circular list.This last list is read by an ISR. It outputs steps to a motor, reads the next delay from the list, programs the timer and returns.This is a quite simple idea, and even more "advanced" then GRBL uses currently. The ISR does not do any calculations at all. It just copies step and direction data from the circular buffer to the output ports, and the timer value to the timer peripheral. It was compiled down to about 40 asm opcodes, and that is inclusive the long push and pop of the many registers the AVR's do (AVR's have 32 general purpose registers, and GCC likes to push / pop a lot of them for ISR's). Back then, you simply could not call a single function from within an ISR, because the penalty was a push and pop of nearly all 32 registers. GCC may have better optimisation now, but back then it was absolutely amazing there was even an (open source) C compiler available to program your microcontroller.The main task for this microcontroller was to keep the circular buffers filled, synchronizing data transfers with the PC and such.The above technique can be used in almost any microcontroller. More advanced controllers these days can do timed DMA transfers, so you do not even have to use ISR's for the steps at all. But that is a bit too advanced even for me.So to recoup, the main things for you is:Do not use software delay loops. They are tempting because they are easy to use, but they waste time.Keep ISR's short, to make room for another ISR as fast as possible. Keep them below 20 or so lines of code, and no loops or waiting for other things in ISR'sApplying these rules will need a change in the way you think about programming. It's also the main reason I dislike "arduino". "arduino" teaches people bad habits. It's enough to blink a LED, but if you want more out of your uC, you'll have to unlearn a lot of what you learned in the first place.

author: rhodges
author_rank: Frequent Contributor
timestamp: September 18, 2024, 12:15:05 am
content: Quote from: Doctorandus_P on September 17, 2024, 02:00:47 pmI totally agree with your post. Thanks for explaining this better than I might have.But I do use delay loops after reset and initializing peripherals that need them. Notably (or only?) the HD44780 LCD controller.

author: forrestc
author_rank: Unknown
timestamp: September 18, 2024, 01:46:47 am
content: Quote from: rhodges on September 18, 2024, 12:15:05 amDelays during init are fine, but once you enter your main loop (or in an ISR) they shouldn't exist. The only possible exception to that is when you need a very short delay (like up to a few microseconds).I use a PIC18F part, which does have high/low interrupt priority, to produce a waveform that has to be on within a couple uS of a signal being asserted, then only on for exactly 100uS, then turn back off. There *is* a delay_us in that ISR. The high-priority interrupt latency is fast enough that I can immediately turn the signal on and be within the time needed, and then the isr having a delay for 100uS to turn it back off is fine. This doesn't impact the operation of the main due to how infrequently this happens.

author: xvr
author_rank: Frequent Contributor
timestamp: September 24, 2024, 07:59:35 pm
content: I just want to add my 5c about ISR: Do not WAIT for anything INSIDE ISR. All waiting activity should performed outside of ISR. You can test some hardware register in ISR for readiness, but if it is not signal ready it doesn't mean that you should wait. It mean that you have hardware malfunction.PS. Do not use 'printf' in ISR (this is not a joke - I seen some thread in another forum where newbie complain for broken interrupt implementation in PIC MCU, because his ISR got wrong data. These data was send to PC for inspection via printf directly from ISR)