20024 FRM4 – Interrupt and Task Scheduling – No RTOS Required

Just another WordPress site

20024 FRM4 – Interrupt and Task Scheduling – No RTOS Required

Hello and welcome to 20024 FRM4 Interrupt and Task Scheduling – No RTOS required! My name is Chris Tucker I am a Senior Applications Engineer at Microchip Technology I work in the MCU08 Division Microcontrollers are everywhere in the world They occupy products we use everyday However the scope of how they are implemented for each application is different based upon the requirements At a basic level those requirements are controlled by a single pin, configured to work Digital as an High/Low capable Input/Output Or setup to work through Analog, possibly measuring a variable input through an ADC (Analog-to-Digital Converter), or outputting through a DAC (Digital-to-Analog Converter) Remember though that we live in a complex world, and often are required to observer and manage multiple task at the same time This is no different for Embedded Systems There are a wide range of Pinout options when it comes to Microcontrollers, supporting a wide range of available Memory and Peripheral Options These supply us, the developers, with quite a collection of resources at our disposal But this great power does carry with it the weight responsibility The responsibility to design a product which performs as expected, behaving as designed, while working at the response speed desired Additionally as coders it is our job to build firmware in a way which reacts with minimal latency, executes at fast throughput speeds, is scalable, efficient and properly synchronized Not to mention readable, and easy to navigate or expand To achieve these goals we will be required to Schedule our operations through Cooperative and Pre-Emptive processing; utilizing simple but powerful techniques To properly ensure a strong knowledge of the required tool for Application Scheduling this class will make sure that “when you walk out of this class you will….” Understand Concurrency, the key principles involved, and limitations Understand how interrupts work, potential problems, and best practices to ensure desired behavior Recognize simple scheduling techniques, and effectively replicate behavior using PIC® MCUs with No RTOS required This class contains a lot of material, each topic powerful in its own way This class will briefly attempt to describe and illustrate the more important concepts Through this installation of knowledge, it is the hope that you as a developer will be better empowered when preparing your application’s system design and operation This class will ask you to think about the concepts, and give you the knowledge to make a capable decision when encountering resource constraints during development The Agenda is a follows: First I will be describing Concurrency Why is it important? What issues could occur? How do we overcome these? Next I will dive deeper into Interrupts What are they? How do they work? When should they be used? Why can they be problematic? After these discussions Lab 1 will be performed to demonstrate and re-enforce the ideas With Concurrency understood we will review Cooperative and Pre-Emptive Processing and how they are leveraged by Microcontrollers for Hybrid Processing Lab 2 will build off these processing concepts, and demonstrate methods to overcome Concurrency issues shown in Lab 1 Briefly we will talk about how Real Time Operating Systems (RTOS) work, what is supplied and general cost of implementation After this the class will outline multiple Scheduling Techniques in their bare form Discussing general implementation and execution Lab 3 continues to build off Lab 2, but focuses more on using the Scheduling Techniques in an abstracted as easily portable/expandable code examples Let’s move into our first Agenda topic Concurrency So what is Concurrency? Simply put; it is a process which executes in a deterministic way from a defined start to end with an expected result In other words, it is the order in which things go from A to B for a single task This may be a single independent action, or set of actions which must be executed in a

set order to reach the conclusion for the required task For example, if I was to enter a room, I would lower my arm, open my hand, grasp the handle, turn it, pull the door towards me, after the door has past my body, I will release the handle, step through the door, reach back and shut it behind me This set of actions are all dependent upon the last, and the process must be completed in whole to accomplish the task Because this is deterministic, I know that if I perform all steps in order without interrupts; the end result will be Entering the Room Performing a single Concurrent Task is easy However things begin to become more complex when managing multiple Concurrent requirements at once Imagine having to Juggle while also Entering the Room Concurrency… Occurs naturally within a system Deterministic within its process Executes to a expected result if repeated without variation Complicated when multiple tasks must occur at the same time May or may not need to interact with other concurrent task Let’s give another example, this time using terminology that will be discussed further during the lecture We are going to brush over these topic now, and dive deeper later This will be done throughout the course to familiarize you with the terms, but always will describe them later in a more technical way The Concurrent process is going to be driving home after a long day of work This is an action we all perform commonly; and I would wager that very often there is little deviation in the steps As a result this process will be relatively deterministic, we are going to exit the building, enter our car, turn on the ignition and drive on our normal path until we arrive home There may be variation in the system which will affect this concurrent process; but we will talk about those in a bit What is known so far is the Concurrent action is to Drive Home, the current actors are You, the driver; like a variable And the Car, the transportation method; perhaps a function call The end result is Arriving Home Looking at it this way it clear that if the driver variable changes, the Concurrent action of Driving Home doesn’t change, the result of Arriving Home also remains the same For you the process may take 10 minutes; for your buddy Bob it may be 20 However those time results are likely to be pretty consistent based upon the driver, or variable referenced This is because the concurrent process of driving home is deterministic; the steps for each actor respectively will not change and thus the results are predictable and expected But what happens when Bob and yourself have to share a Resource such as the road? You both are in the middle of the concurrent process of driving home You both want to go from start to completion as quickly as possible Since you share the road however, if neither of you yields there is going to be a problem This is the same common problem we have with Microcontrollers However, our shared resource is the Core CPU Our processor can only complete one concurrent task at a time So we are going to have to have some coding logic in place to make aid in smart decision making Returning to the road; this is taken care of by a Traffic Light This gives us a “Rule for Access” Basically something which determine what actions to perform when in specific states This is Synchronization Light is Green, Go Light is Red, Stop That is an example of a Mutex implementation We can choose to do one of 2 things Light is Green, Go Light is Red, Stop Light is Yellow, Slow or Accelerate based on distance; a condition This is more of a Semaphore example We may perform a few actions based upon Conditional Variables Management of the traffic light can be Scheduled in a few ways Two such examples are: Periodic, where the road access is alternated between North-South bound traffic, and East-West bound traffic Event Driven, where the road has sensors which detect cars waiting and makes logical choices when to change states based upon the data available

Regardless of the type of traffic light scheduling done; some things are clear First the access to the crossroad must be alternated If it is not then one direction will end-up in Deadlock, where the cars will never be allowed to move Defiantly can’t have this Second the access to the crossroad must be fairly distributed, everyone needs their turn to use the crossroad If the road access is disproportionate it will lead to Starvation Where one direction is given more access then another, this will result in quite a long backup on the roads Probably won’t work out well for the city Deciding which Traffic System is best and how road access is given is up to the Traffic Planner In your system these decisions are up to the system specification, and/or you the programmer On the way home, Bob decided not to follow the “Rules of Access” and attempted to claim part of the road which was currently in use by another car As a result there was an “Interrupt” causing us to alter our process path to arrive home Luckily we are a good driver following the “Rules of Access” and were able to react with fast enough “Latency” to avoid the incident without major performance issue Because of the detour we have to travel differently than our normal path In the end we will still get home, but now it may take a little longer Because Bob did not follow the “Rules of Access” he is now “Blocking” Best case the accident is going to result in “Delay” till it is cleared, worst case it will result in “Deadlock” if the road become closed Luckily there is a side road we are able to drive down We had to slightly modify some the variables (such as connecting roads) of our “Drive Home” task Doing so has injected “Jitter” on our total time There is something unexpected which resulted in a longer then ideal “Through-put”, as result we have longer “Latency” before arriving home From the Scheduling perspective what we can take from all this is, do not create “Blocking” conditions which “Delay” unnecessarily Systems should be designed with small “Latency” to react quickly for critical actions, minimalize task “Through-Put” to ensure system performance capable of accommodating possible “Jitter” injection It is important to remember that Cars are not the only “Actors” which may occupy the roads In fact some will have a higher “Priority” than the cars, for example Pedestrians Imagine a nice Grandmother being escorted across the street by a friendly neighborhood child Obviously their safety is more important then you getting home to the latest TV episode In the car we are going to come to a complete stop, waiting until they are clear before moving on This is a example of “Basic Priority” It is more likely that a car will be in the road, but if a Pedestrian is present; the car must yield That is another “Mutex” implementation More “Advanced Priority” task handling is done in the same manner, just with more conditions for consideration For example, you have to turn left There is a pedestrian crossing directly in front of you Additionally, there is another vehicle across from you going straight According to the “Rules for Access”, you must allow the Pedestrian to cross, allow the straight car to go, then you can turn left This is again a form of “Semaphore” synchronization, and makes logical choices based on “Conditional Variables” which may change states during normal system operation The end Result is Arriving Home Because of Traffic, or Accidents Length to arrive home may vary But the result of the Concurrent Process of “Driving Home” will always be “Arrived Home” Note that even if it was us and NOT Bob that was in the accident We will eventually “Arrive Home”, but the “Jitter” injection will be much larger After all that we are back on So in reflection: The Concurrent Process was: Driving Home Main Resource: The Road

Shared with: Other Cars & Pedestrians Rules of Access: Traffic Lights, Lane Merging, 4-way Stop Traffic Laws, Pedestrian Priority Deterministic results: Arrive Home Ideal Length to Home Throughput: 10 Minutes Jitter Caused by Traffic: 4 Minutes Jitter Injected by Accident: 12 Minutes Latency: 10 – 22 Minutes Typical: 12 Minutes Now let’s look at the more typical Concurrent Task requirements of an Embedded Application The Microcontroller must manage: Manage a LED Driver Monitor Battery Voltage Communicate with LCD through I2C Manage Menus and Setting through LCD Monitor and Measure Potentiometer (dial) position through ADC Detect Smoke & CO2 levels Control Alarm Speaker Looking at these Concurrent Task it is apparent that some can be processed Independent while others may be dependent upon data or result of others Let’s look at just (3) of these possible Task: I2C Communication managing the Touch LCD This Task operates independent Monitoring the LED Driving via the PWM (Pulse-Width-Modulation) peripheral This Task is dependent upon another Measure Potentiometer position via the ADC (Analog-to-Digital) peripheral This Task operates independent, but is required for another Task For each of these Concurrent Task were to be processed at one time we would require (3) independent Processors However, Microcontrollers only have a single (1) processor available for use As a result when performing multiple Concurrent task with a Microcontroller; we instead must use Virtual Concurrency This is when a single processor is responsible for execution of multiple concurrent task To accomplish this the processor must allocate time to work through each task independently If one task is dependent upon the result of another; then the order or execution may be very important Again looking at the same (3) Task, but this time only using a single processor In this application, the LCD is managed, then the ADC is used to “Produce” a value result, which is “Consumed” by the LED Driver to update its behavior Through this Virtual Concurrency; the Task are processed “Cooperatively” in “Sequential” execution Comparing the Real World example of Driving home with an Embedded Systems design, parallels to the properties can be drawn easily The Cars and People Actors in the real world become Task, Threads and Processes of Embedded Systems The Shared resource of a single road is like the single code processor of the MCU, number or lanes mirrors a devices available Program or Data Memory Alternate road paths could be supported Peripheral Register Rules of the Road become Rules of Operation and execution for the Application design Car Turn signals are like Flags indicating our system wants to change operations Access is given to first Pedestrians or Police Cars, just as Higher Priority Task must be handled over Low Priority Task At the end of the day there must a “Deterministic Result”, returning home from work, or executing

all Application Task with the correct resulting behavior as designed by the product specification The largest issue which effects a system processing task concurrently is called a “Race Condition” Race Conditions are defined as “The behavior of a system where the output is dependent on the sequence or timing of other uncontrollable events” The bugs that rear their ugly heads as results of Race Conditions do so when events do not occur in the order which the programmer intended Because the nature of how Race Conditions occur in a system they are difficult to repeat, and thus to find and debug I refer to these as “Heisenbugs” which is defined as a software bug which seems to disappear or alter its behavior when one attempts to study it This is a result of variations in system operation typically related to timing as a result of working from within a Debugger, or Debug state Let’s again look at a Real World situation to better understand the Race Condition concept; and more importantly its possible repercussions For this example, lets think about a Bank Accounts How about my account in particular Assume that the account is shared by my Wife and I Each of us have our own bank cards issued granting full access The account currently has $100 in it My wife would like to pay our bills from her phone I want to buy the latest video game, so go to an ATM to get cash Assume both the Bill and the Video game are $40 a piece Theorize that we both attempt to do a withdraw at the exact same moment What would happen? The behavior we would expect may look like this: A balance of $100 exist in the account From her phone my wife’s views the balance, “Reading” an account balance of $100 from the Server She request to pay the bill of $40, “Writing Back” to the server the updated value of $60 After this is completed I request the balance from the Server and “Read” $60 With this knowledge I withdraw $40, and the ATM decreases the balance The ATM then “Writes back” the value of $20 to the Server That is what we would expect if everything works correctly But what would happen if a “Race Condition” was to occur and the system was not built with the appropriate code “Guards” logic If a Race Condition occurs, something like this may occur: My wife request to view the balance and see’s $100 At the same moment I do the same, and also see $100 Both us request the $40 transaction; again at the exact same time Independently the Smart Phone and ATM both assume the balance is at $100; so they subtract the value of $40 from that and get a result of $60 The devices then “Write Back” to the Server $60 Now instead of the account reflecting $20 as it should; it thinks there is $60 That incorrect value is created because of the Race Condition This bug may be great for me, but not for the Bank; and defiantly not for the Bank’s programmer If the Bank System used “Synchronization” properly this issue wouldn’t have been created Before we move on to how to resolve “Race Conditions”, let’s look first at a non-theoretical Race Condition One that can easily occur on a Microcontroller PIC16 and PIC18 devices work using 8-bit registers, meaning only 8-bits of data can be exchanged at a time What happens when working with a Peripheral which operates at 16-bits; a peripheral like Timer 1 When reading the active count of Timer 1; the value is stored in 2 separate registers:

TMR1H (high), and TMR1L (low) Peripherals operate truly concurrent to Microcontroller code execution This is “Parallelism” which will be further described later in the course For now, just understand that peripherals work independently at processing their concurrent task Now let’s suppose that the High register has a current value of 0x00, and the low is 0xFF Our code requires to know the Timer value Since it is an 8-bit architecture we must read both high and low register separate First we read the High register and see TMR1H contains 0x00 While going to read the Low Timer Register, the Peripheral “Ticks” and thus increments the active count This increments TMR1L which “rolls-over”, returning to 0x00 triggering TMR1H to increment by 1 Now when we read the Low register a value of 0x00 is returned for TMR1L The result is that our Code believes Timer 1 active count is 0x0000, while in reality it is 0x0100 That is a Race Condition; and can be a very bad one if your system does not have proper “Guards” in place There are a number of ways to protect your system Primarily your concern is to protect the critical section One of the most simple ways to ensure proper value return is to “Stop” you Peripheral This can be done with Timer 1 by clearing the TMR1ON bit Doing so prevents the active count from changing when executing the “critical section” which reads and stores the register values After the “critical section” is completed, setting the TMR1ON bit resumes peripheral operation This is not the only solution; it is just a simple example which is “cheap” in code If the peripheral cannot be paused for system design reasons Another method could be a “Redundant Check” In that method the code would read the High, then Low, then High Register again Then compare the 1st and 2nd High register reads to confirm it they do not differ This is more “expensive” in code because it requires more operations, and math comparisons In the end these are all forms of “Synchronization” “Stopping” the peripheral prior to reading the register values is a form of “Mutual Exclusion” Often called “Mutex” for short “Mutual Exclusion” is a synchronization method that exclude other processes from gaining access to “critical sections” of code This allows for the processing of “Atomic operations” “Atomic Operation” (s) is when a Concurrent Process is isolated, insuring that it will run from start to finish without being stopped or interrupted Typical flow of an Atomic Operation is to enter a Task, usually within a function call “Lock” the system For Microcontrollers this means that all peripheral interrupts must be disabled They can continue to run, but the appropriate “Enable” bit must be cleared to prevent code jump into the device interrupt vector This can easily be done by clearing the General Interrupt Enable (GIE) and Peripheral Interrupt Enable (PEIE) bits We’ll describe Interrupts deeper later After “Locking” the system The required process is begun and allowed to run Once it has reached completion of its critical section, the system is “Unlocked” and returns to normal operation In general true Atomic Operations are rare

True Atomic Operations cannot occur “Jitter” as they are so timing critical The majority of process can actually just implement a “Mutex” design for synchronization “Mutex Flags” are Boolean variables used to signal a task execution upon a request or event These are the best ways to ensure efficient full application processing, without wasting time on unrequired task execution Mutex Locks” is a variable that ensure only one task uses a shared resource which contains a critical section On a Microcontroller a good example would be an ADC A product may have multiple sensors, but there is only a single ADC on chip This means the task which handles reading one sensor, must make sure the ADC is not currently measuring a value for another sensor’s task If a “Mutex Lock” design is not done, the system will run the risk of one task taking the value provided by a different task sensor In systems with multiple processors; “Mutex Locks” are more accurately used to lock threads from being accessed by multiple processes at one time “Mutex” designs are always Boolean; and so are True or False When the State is True, the “Lock” is already claimed If False, the “Lock” is currently free and may be requested “Mutex” Flags which are shared between multiple Here is an example where (3) separate Task share system resource This resource may be a use of a peripheral, or something like a encoding function Here we can see how it would work Task A, B, & C all share the same resource Task B is first to request it’s use; which returns that it is available Task B then Acquires the lock, claiming the resource Task A next request the resource; which returns that it is in use and unavailable Task B is done using the resource, and so Releases it; freeing the lock Task A again request the resource; which returns that it is now available Task A Acquires the lock, claiming the resource Task C next request the resource, which returns that it is in use Now if we were to apply the “Mutex Lock” design to the Bank Activity example it would look like this From the Smart Phone my Wife “Request” to pay the bill From the ATM I “Request” to withdraw money The Smart Phone “Acquires” the “Lock” from the Bank System Allows the transaction to occur and updates the balance The Smart Phone “Releases” the “Lock” The ATM then “Acquires” the “Lock” Processes the transaction and updates the balance The “Lock” is “Released” Here is an example of what a withdraw may look like in code A withdraw of “amount” is to be performed The system “Request” the lock on the Bank Account The “Request” comes back as false indicating the lock is free The lock is “Acquired” and the critical section of modifying the Bank Balance is completed The lock is “Released” and the withdraw returns as “true” indicating it was successfully performed Note that if the “Request” of the lock returns as true; the withdraw process will automatically return “false” indicating the withdraw failed to be performed

“Semaphores” are variables or abstract data types that are used for controlling access, by multiple processes, to a common resource in a concurrent system Where as “Mutex” were based off a single results “Semaphores” are based off the results of many different variables Most common implementation of “Semaphores” are through the use of Binary Flags, Conditional Variables, and a combination of these If using strictly Binary Flags; then (2) flag conditions would be required to be met for processing of the action This means that if checking (2) different binary flags; there is a possibility of up to (4) corresponding actions which can be executed With conditional variables that number of checks and corresponding actions can grow quite large based upon the design For example, and 8-bit value can range from 0 – 255 Which means used for a “Semaphore” there could be up to 256 separate actions based off a single variable’s value Let’s apply a “Semaphore” to a Library Study room analogy In this example, there are (3) Study Rooms The number of available rooms, or “Free” rooms is represented by the “S” variable value Students must check in at the Front Desk, and “Request” a room If one is available, they may “Acquire” a room If none are available, they are welcome to wait on the couch until a room has been “Released” Our first student arrives early to find all room available They put in a “Request” and “Acquire” a study room right away Another student arrives and “Request” a room The 2nd Student “Acquires” their own study room A 3rd student arrives and “Request” a room They are lucky and “Acquire” the final room Another student who slept in arrives to “Request” a room They are told that all the study rooms are “Locked” and being used The student goes to the couch and waits for the shared resource, the study room, to “Free” up Lucky for them, the student who arrived 2nd only had to study for a short test So they “Released” the room after only a short wait The 2nd student had a fast “through-put” to complete studying As a result the shared resource was freed up This allowed the 4th student to “Acquire” the room to complete their task of studying In this example the Students represented an application Task requiring processing The Study Room’s were shared resources which could represent program memory space, peripheral usage or even execution cycles The Front Desk was the Code logic controlling the study room resources and management of the S variable value Here is where the “Mutex” and “Semaphore” implementations matter The Couch represented a Queue which is a Scheduling technique It will be discussed later “Producer-Consumer” design is another basic method to synchronize two task where at least one is dependent upon there other This dependents means that there is a relationship between the task This add to system complexity Understanding these system relationships in application design is fundamental to Scheduling Lets illustrate this point a little further by again using the Library Study Rooms The blue face represents a “Consumer” who does something to the rooms with a specific “State” is true In this example the orange face represents a “Producer” who is responsible for managing room “States”

The blue face represents a “Consumer”… …who must wait for the “Producer”… …to complete its task After which the “Consumer” will used the result for its dependent task The “Producer” then must wait for the… …”Consumer” to complete its task… ….before it may resume normal Operation Those are the basics of synchronization So lets now move into some System Design Concerns “Starvation” is when system resources cannot be dedicated enough for stable task performance “Starvation” can result from many performance, efficiency, or scalability issues; but often presents itself most with task priority Lets use the Library Analogy one more time This time there are High Priority Students, and Low Priority Students A Low Priority Student arrives to find that High Priority Students occupy all the Study Rooms So the Low Priority Student takes a seat to wait Shortly after another High Priory student arrives The rooms are still full So they sit next to the Low Priority student Right after sitting down One of the Study rooms becomes available So the High Priority student gets up from the Couch and takes the study room The Low Priority student keeps waiting After a little while another High Priority Student arrives Now the Low Priority student is getting a little worried Then a room opens up The High Priority student takes the room The Low Priority student is hopeful to get a room to study in Until another High Priority Student arrives This is how starvation occurs It is also why it is important to write functions execute with fast “through-put” and do not “block” in code when the processor could be handling other task operations “Deadlock” occurs when a task fails to release a lock, or two or more competing actions are each waiting for the other to finish An example of this would be if Task A and Task B both require the possession of variables Lock1 and Lock2 to operate to completion Both locks can be Acquired separately and are unaware of each others state An unguarded race condition occurs and Task A Acquires Lock 1 right as Task B Acquires Lock 2 Task A now requires Lock 2 to reach completion, while Task B needs Lock 1 Neither can release their locks because the task process has started but not completed They have reached a “Deadlock” and the application will fail A common occurrence of “Deadlock” with Microcontrollers is when a “Lock” is shared by Main and Interrupt Code This creates an opportunity for a Task in the Main code to “Acquire” the lock An Interrupt then occurs which goes into the Interrupt Server Routine (ISR) code were the lock is required, but cannot be released

This would prevent the ISR code from executing to completion, resulting in waiting forever The effects of “Starvation” on a system are typically temporary, but may plague the system and occur at a high rate This is reflected by major performance issues and variation in an application Following the “Keys of Concurrency” will help to reduce risk of starvation “Deadlock” however will occur and will not resolve itself “Deadlock” conditions often occur as a result of a “Race Condition” Abstracted, non-dependent, and well architected code reduces risk of “Deadlock” on a system When working with a system operating multiple concurrent processes and task there are a (3) key concepts to understand These are: Performance, Efficiency and Scalability Good Performance is based on task throughput, system latency and effects of jitter “Throughput” is a Task execution length from start to finish This can be a measurement of time or cycles The main idea of having a small throughput is to make sure that when executing a task it is not wasting time doing nothing If it is waiting to “Consume” data which is not ready Let the system do another task and return when the data is ready “Latency” is the length of operation between triggering requested action, compared to when it actually completed the service The lower the system latency, the better response time the application will have “Jitter” is the variation of Latency It is typically created when one task interrupts or accreting priority over another during operation This would cause the first task “throughput” to increase The variation between its normal ideal case, and the actual length would be the “Jitter” injected on it System Efficiency comes down to best utilization of resources This means small purposeful functions which only perform task when necessary by utilizing “Mutex” and “Semaphore” practices This good design will help make sure every task will always finish and result in the expected result Optimizing the throughput of a system will allow it to better accommodate Jitter and Latency constraints Scalability is important with code and system design A task is scalable if modification to dependent Task do not negatively effect the throughput capability of an isolate task This means that if I am a “Consumer” task and the “Producer” task was modified and expanded, its changes did not effect the “Consumer” throughput length It should have only had effect on the “Producer” throughput The task can be configured to accommodate multiple operations which improve or only mildly effect throughput length This is done easily if processes are well isolated to a purpose, instead of being responsible for many things Scale up and Scale out are important ideas on design Scale Up basically means that the task or process is refined and well defined for a strict purpose applicable to only your application or product It is very specialized, this allow for a tight specification of work and design Scale up is great for Embedded Systems which typically have limited resources Scale Out means that you are create a task or process with a broadly defined ruleset It will be required to accommodate a wide variety of features This makes for a better general adaption process, because it is easier to work with The cost is that typical a large amount of resources are required to support the broad

implementation Scale Out is good for Computer or Software Systems where resource are more available What tools does a Microcontroller have The most powerful tool which Microcontrollers grant us as developers are Interrupts Next would be the dedicated on-device hardware logic that allows for Parallelism That hardware are known as Peripherals, and they are what allow for easy Scheduling with a Microcontroller Let’s now discuss Interrupts What they are, how they work, and some rules to keep in mind using them Interrupts are signals generated which cause the core CPU to pause executing code currently running Configured Interrupts trigger the management of Context-Saving of important register information to allow for safe switching of code execution They allow the system to operate more responsive to time critical task, and can improve efficiently when used correctly Most importantly, it allows for the Microcontroller to support Pre-Emptive processing Interrupts are widely configurable based upon the desired application usage Interrupts work through dedicated hardware logic gates built into the Microcontroller They operate fully concurrent to the code execution; and so response speed is extremely fast The Global Interrupt Enable (GIE) bit is responsible for all interrupts, “Core” and Peripheral It is the master access control If cleared it will prevent all Interrupt Events from remapping to the ISR Peripheral Interrupt Enable (PEIE) bit is responsible for only the Peripheral Interrupts If cleared it will prevent Peripheral Interrupt Events from remapping to the ISR The INTCON register manages “Core” Interrupt Bits This main Interrupt Control Register It manages Timer 0, an External Interrupt Pin, and typically a Interrupt-on-Change Interrupt Bit name end with either “E” for Enable, or “F” for Flag The “F” bit will automatically be set by the Hardware If “E” is clear, the “F” bit can be read and processed through software Setting the “E” bit will allow the Microcontroller to pause main code execution and remap into the dedicated vector location in the Program Memory where the Interrupt Service Routine exit On PIC16 this is 0x04, on PIC18 it is 0x08 for Low Priority ISR, and 0x18 for High Priority Into the PIC24 and PIC32 devices it becomes very specific and reference to the datasheet is recommended However, the general way it works remains the same throughout architectures When a configured interrupt event occurs, the main-line code execution is paused It is remapped to the vector location of the ISR The Context-Save of (12) core register is done to ensure the main-line code is restored exactly where it was before the interrupt triggered The values stored in the (12) core registers seen here are copied into “Shadow Registers”

which occupy a dedicated memory location which guarantees safe context switching between main-line code and ISR The (12) core register will be required for ISR code operation Devices such as the PIC18 which have a High and Low Priority Interrupt support corresponding “Shadow Register” counts So PIC18 devices have (2) Shadow Registers to support the ability for a High Priority Interrupt to pre-empt a Low Priority Interrupt during code execution The Address location where the main-line code was, is “pushed” to the top of the hardware stack, and later is “pulled” upon restoration allowing the code to resume correctly With the safe Context-Switch achieved, the code located in the ISR is executed The ISR can be written completely in C code; just like the main-line code The function is identified by the qualifier __interrupt, it has no parameter and is a void return type The code ISR should not be called by the main-line code If that type of operation is required it should be triggered using a peripheral interrupt The compiler encodes the function specially, and handle linking routine to the interrupt vector automatically Upon entering the ISR, the GIE bit is cleared by Hardware After completion the retfie instruction is used as a return and includes restoring the GIE bit to set After ISR code is executed, the Context of the main-line code is restored After ISR operation is completed, its use of the (12) core registers is completed The values retained in the shadow registers restores the original main-line code values The code execution address is “popped” from the hardware stack, and the main-line code resumes processing from its previous location The main-line code will continue to be executed until the next Interrupt Trigger This is what an interrupt would look like in Code The Main code has configured Timer 2 Peripheral to generate a periodic interrupt This was enabled by setting the TMR2IE bit inside the Peripheral Interrupt Enable 1 register Since the PEIE bit is set, this interrupt allow peripheral to trigger ISR entrance Since the GIE bit is set, upon the interrupt the code will enter the ISR The Main line code does nothing, just waiting for the configured timer event to occur When the Timer overflows, the TMR2IF bit is set by the Hardware This triggers the entrance to the ISR, where the GIE bit is cleared Then Context saving of the Core Registers is done into the Shadow Registers The main-line code address is “pushed” to the return stack With Context Saving completed, the code begins to execute from the ISR

The TMR2IF flag is checked if true It was set so, the periodic Timer related task is handled After task handling is completed, the TMR2IF bit is cleared by software This code is very important as if the bit is not cleared, the code will become stuck in the ISR Any remaining Interrupt related Flags will be checked After completion of all ISR Flag handling, the code will exit the ISR function The “Shadow Registers” will be used to restore the (12) Core Registers The GIE bit will be restored to set by hardware The “Return Address” will pop from the stack and resume main-line code execution What tools does a Microcontroller have The ISR is very powerful when used correctly Some o the potential problems include handling of Race Conditions, Latency, Jitter Injection, the blocking nature of Interrupts and a False Sense of Priority some time associated with Interrupts The most common cause of Race Conditions related to ISR operation are typically a result of direct global variable manipulation and usage from multiple locations in the code For example here the 16-bit variable I is declared globally and is incrementing in the main-line code In the ISR, Timer 1 is being used for a periodic event, and the value of I is checked as a “conditional variable” which controls if the process is to be executed or not This is the same Race Condition that was discussed earlier I is 16-bit and the architecture is 8-bit It was half way through variable value update when the interrupt triggered This resulted in the ISR conditional value check again 0x150 to be incorrect, and as a result the process was skipped when it should have been called Again the simple solution is to clear the GIE bit, and prevent interruption while updating shared variables In general it is highly recommended not to use Global Variables in this way Instead abstract variable management using get and set functions Some times when attempting to narrow down Race Conditions which occur between main-line code and ISR it is helpful to declare suspect variables as “Volatile” “Volatile” means that the compiler must assume that the variable can be changed by external factor, and should not assume that it remains the same value during optimization It is equivalent to saying “Don’t Touch Me” ISR Latency is important to consider Suppose that there are (3) Peripheral Task being handled in the ISR If all (3) of those Peripheral Interrupts were triggered in synch; then upon entering the ISR all (3) Flags would be set awaiting processing The 1st Flag would be check, its task executed and Flag cleared Still in the ISR, the 2nd Flag would be check, its task would be executed and Flag cleared Only then would the 3rd Flag be checked, its task executed and Flag cleared Minimal Latency would be different for each task However, if only the applicable interrupt was triggered, the length of time to complete

that task and exit the ISR would be the Minimal Latency Maximum Latency would always be the combination of the Minimal Latency of all possible task that exist Note that Latency also exist in the main-line code Main-line code and ISR Latency are relatively independent from one another “Jitter Injection” however is directly related to the main-line code and ISR relationship ISR task operation cause “Jitter Injection” onto main-line code “Jitter” can be though of as how long the ISR blocked the main-line coder execution The ISR can be considered Blocking by nature of its existence If a task is being processed in the ISR; then the Main-Line code Task are not being handled So if the longer ISR task take, the more “Jitter injection” occurs; which increased main-line code Maximum Latency and all around wrecks performance This is why it is very important to have short task “throughput” execution from the ISR The golden rule of the ISR though; is to make sure to clear Flags after task handling Or you will forever be stuck in the ISR, never to return to main-line code Finally, remember to not fall prey to a False Sense of Priority related to the ISR Just because a Task is put at the top of the ISR does not guarantee it will always be processed always at minimal latency Imagine that Task 1 requires extremely low latency The ISR only manages (3) task Task 3 Interrupt triggers ISR entrance While Processing Task 3 Task 2 has its interrupt triggered After Task 3 completes processing, the code moves out the bottom of the ISR As soon as the GIE bit is restored, the ISR again is triggered for entrance by the Task 2 Flag The code loops back around into the ISR and just starts to Process Task 2 Almost right away Task 1’s Interrupt Trigger occurs Followed by Task 3’s Interrupt again Since Task 3 is checked next in the ISR sequence, it is processed AGAIN… …prior to Task 1 finally be processed This is why if a Task requires near instant response where Latency variation cannot exist; Multiple Interrupt Vectors are required These ensure Context-Saving across multiple Shadow Register, and hardware priority handling It is always for very time sensitive task, Interrupt Isolation is recommended Different PIC architectures offer different options On PIC16 currently there is a single ISR On PIC18 currently there is a High and Low Priority ISR PIC24 and PIC32 have a collection of vector interrupts with various priority configuration It is likely the vector interrupt design will exist on some 8-bit devices in the future In review, some general rules of the ISR are: Keep Task Through-Put short, this improve system latency, and mitigates jitter injection Avoid anything blocking or waiting data when executing from the ISR Do no relay on the order in which the ISR task are processed Avoid use of shared global variable between main-code and ISRs

Moving on to Lab 1 This course is supported by (3) demonstration Labs; as well as Flow Chart to easy show application design The Intention of Lab (1) is to have a simple application which has not followed the “Keys of Concurrency” or the “Rules of the ISR” It will quickly and easily demonstrate effects of Jitter, and Latency on performance It also demonstrates a product which would not be easily scalable, or able to quickly add last minute features Lab (2) modifies the application of Lab 1, adding in a basic Scheduler and using Mutex and Semaphore system design for improved performance The code is less abstracted and easier to follow then Lab 3 Lab (3) supplies a more extensive application Scheduler, using the Techniques discussed further in the lecture While the Labs are meant primarily to demonstrate the ideas of the lecture, they too should should supply a basic framework to develop future application upon To perform the Labs, the only Development Tool requirement will be the: MPLAB® Xpress Evaluation Board It is populated with a PIC18F25K50 which allows for easy drag and drop programming of the Host Microcontroller, the PIC16F18855 In addition to managing programming of the PIC16 device The PIC18 handles USB CDC emulation, allowing for USB-Serial communication to the PIC16F18855 The Xpress board supports (4) LEDs, a push button, a Potentiometer, I2C temperature sensor and generic mikro Click board connector for quick development and prototyping Lab 1 will demonstrate use of Interrupts and Concurrency Issues cause by lack of proper code Scheduling design The objectives of Lab 1 are to: Demonstrate Concurrency issues which are physically observable on the board Illustrate in code poor programming decision which cause large impacts on system performance Visually show effect of latency on main-line application processing Observe “Jitter Injection” as a result of lengthy ISR throughput task In conclusion of Lab 1: It demonstrated basic interrupt triggered task processing Allowed the user to experience the effects of throughput, latency and jitter on system performance Created Blocking & Inefficient code which visually show repercussion of blocking code, as well as large ISR jitter injection Next to interrupts, the most powerful tool in a Microcontroller’s box are their Peripherals Through use of Peripherals Microcontrollers are capable of achieving Parallelism “Parallelism” is defined as the state of being parallel or of corresponding in some way With Computer Systems it is the use of parallel processing Microcontrollers achieve this by having dedicated hardware logic built in to handle Peripheral processing independently from the Core Code being required to interact with it After configuration or setup, the peripheral will work in the background continuously executing its concurrent task until stopped or reconfigured Upon reaching completion of the task, the peripheral will make available to result It will then repeat the concurrent action regardless of if that result was taken and used by the code It’s processing is independent from the code being executed Usages can include monitoring a sensor input value, driving a modulated signal output, or leveraging the internal hardware logic to effect behavior of a connected external device Implementation of how the peripherals are used can vary depending upon design requirements

Some examples may include: Using an ADC peripheral but with the “Enable” bit cleared In code the “Flag” bit is monitored When set, the Peripheral has completed the conversion and ready for the main-line code to read the result from the register for use This would be an example of “Cooperative” processing Another would be using the I2C peripheral, this time with the “Enable” bit set Upon requesting a I2C communication, the ISR will be entered and process the bus communication After it will release control back to the main-line code This would be an example of “Pre-Emptive” processing A third example would be using the COG peripheral, which is one of Microchip’s Core Intendent Peripheral (CIP)s After initial configuration, CIPs require no interaction with the main-line code, and also do not require execution from the ISR They are truly independent concurrent task execution The only resource consumed to support CIPs are the hardware pins used as input/output signal paths No Core execution cycles are used as all functionality is handled by dedicated internal logic gates Lets distinguish standard Peripherals from Core Independent Peripherals once more Standard Peripherals will execute concurrent task without consuming Core cycles through Parallelism Upon result completion, the Core will be required to used instruction cycles to “Request” the result In code it will then require instruction cycles to “Process” the response Even if a Interrupt is generated upon Peripheral result completion, the Core is required to “Process” the task request through the ISR Core Independent Peripherals also will execute concurrent task without consuming Core cycles through Parallelism However this time upon result completion, dedicated hardware logic will automatically output the result without need for a “Request” The result signal is outputted automatically external, or even internal to the Microcontroller with no Core Cycles consumed CIPs do not require ISR execution That is the minor but vital distinction between Peripherals and Core Independent Peripherals A CIP can be configured to behave or be sued like a standard Peripheral, but Peripherals are not capable of achieving all that a CIP can Here is a small example of implementation of both Standard and Core Independent Peripherals on a PIC16F1614/8 device This design is intended to control a Single Phase Brushless DC Motor using a Full Bridger Driver Circuit In the past doing this would require a lot of math and logical choices in Code Now everything is being handled by the CIPs and dedicated hardware This results in a massive increase in response time capability as a result of hardware always being faster then software On top of that, the majority of resources are now freed up on the Microcontroller With its only responsibility now being to process EUSART communication, and monitor a single ADC channel The ADC works as a emergency shut off incase of dangerous temperature levels Otherwise the entire system is only dependent upon an external PWM signal, and the desired behavior of the Motor is managed by the CIPs That covers all the fundamental concepts which are required to be understood before developing a Scheduler for your application Those were the tools

Let’s now discuss how to best use them “Scheduling” is a method which determines the order, and duration of task execution run on the CPU Schedulers are best to ensure managing the Concurrent task of an Application Keep the CPU busy doing work which is required instead of redundant or unnecessary task Maximize throughput by being able to shuffle task processing Minimize Latency with good design to ensure agile and responsive behavior Allows for Maximizing of Fairness between all task competing for resources Makes the requirement of meeting deadlines easier to maintain There are (2) real Scheduling Processes: Cooperative Processing and Pre-Emptive Hybrid Processing is a combination of the two Cooperative Processing is a type of scheduling where the CPU does not force a switch in task processing if currently executing a task Instead, task cooperatively relinquish control voluntarily after completion Cooperative Processing is the most common, and is what can be considered the “main-line” code Within the While(1) loop of the main-line code are a collection of Task which will be processed one at a time, in sequential order Task cannot interrupt or Pre-Empt another Once a Task has begun execution, only it may relinquish control for another Task to begin The code for this would look very familiar The Cooperative code after initialization and startup configuration will Print “Dispatching”, execute Task 1, Task 2, then Task 3 It will then finish execution of all Cooperative processes Then it will loop back around in the While(1) The Purpose of Cooperative Scheduling is to: Manage Task through Multi-Programming Synchronize variable and Peripheral interactions Handles standard operation of the application Typically used for Idle, Low or Mid Priority Task which required periodic or reoccurring processing General Concerns for Cooperative Scheduling should be: Possible Corruption of task execution which results in unexpected system behavior Task which fail to release, creating system “Deadlock” Design should account for possible Latency variation Understanding effects of Jitter on system important Pre-Emption is a Scheduling Process which allows the CPU to switch task processing regardless of execution location This allows for a task to interrupt another task, and claim the Core Processor for execution of its process Interrupt processing can be considered “Pre-Emptive” This is because while executing code from the main-line, an Interrupt can occur which “Pre-Emptively” remaps to the ISR vector Once in the ISR, all task are processed Cooperatively This is because if in the middle of processing a task in the ISR It will be required to relinquish control for the next ISR task to be processed On a PIC18 for example: Main-Line code is Pre-Empted by Low Priority ISR, which can be Pre-Empted by High Priority ISR True Pure Pre-Emptive Systems are far more complicated and require large amounts of dedicated resources for system stability Remember that on a Pure Pre-Emptive system, every task must be able to yield to another

task at any point of execution This means whatever the task is, is task is independent in its own operation and can be considered basically its own application So each application would have to maintain it’s function call stack, variable stack and any other relevant context This is a huge amount of resources required for every operation which the system may be responsible for But that is the requirement for guaranteed stability It is an all or nothing kind of situation You must pay the cost in resources That equates to larger, more expensive components Each required task would have to be it’s own individual project with its own “Reset Vector” position offset in the Program Memory The Application would be responsible for an individual task Push Button Monitoring, Sensor Measurement, Menu Navigation Handler, etc… Each will have it’s own main function which it will typically run to completion Any required retention of values to alter operation must be stored in a known address location for future reference After completion of the task code it is the applications requirement to restore its own execution address back to its original “Reset Vector” location it was offset to This prevents processing into a different applications dedicated Program Memory location If one task application was dependent upon a shared variable For example the Menu Navigator needs the state of a button Then both applications would have to be aware of the specific memory address location where that variable is stored Since it is a shared variable, a 2nd address location would be required to act a “Mutex Lock” status indicator These required interactions, and low level system knowledge makes pure Pre-Emptive systems very complex difficult to develop Consider the number of task typically managed by a single Microcontroller think of the number or resources that would be required Let’s now consider how this pure pre-emptive design would operate on a PIC16 device Upon reset the part would start code execution from 0x00 It would execute system initialization which configures all hardware for operation across the part It would check if it should stay in the Bootloader which worked as an Atomic Blocking operation If not requested the code would enter the while(1) loop, until the systems periodic timer overflows Once this occurs it will enter the ISR, and never return to the while(1) loop It will now forever operate from the ISR Now within the ISR it will process the clear the Flag Restore all of application 1’s Parameters, consisting for function call stack, variable values and stacks, current Address executing from and any other required information The code will then begin to execute that address location, where ever that applications “Reset Vector” was offset to It will execute its self to completion, then restore its own dedicated code execution Address location Upon the next Periodic Time event, the Flag will be set returning code execution to the ISR All Application Context information must be stored off The Next applications Context information is inserted The ISR is then exited, because of the Context information code execution will begin at the new location This type of design through use of a single Timer, and manual context management is typically what RTOS’s will do on a PIC RTOS will leverage the compiler to create its Pre-Emptive system, reducing some resource cost and management headache

As discussed previously though, it is a Scale Out design; and will carry overhead cost Instead of just a Periodic Timer it is additionally possible to set up other Interrupts like a Interrupt-on-Change on a push button to prioritize restoration of it’s management application But additional resource would be required to manage this logic It also increase risk of “Starvation” The purpose of Pre-Emptive Scheduling is to: Process Task with minimal Latency upon Triggered events, External or Configured Manage Time Sensitive processes which must be handled upon, or very close to an event trigger Modify / Update system parameter effecting the way Cooperative Processing occurs Some Concerns with Pre-Emptive Scheduling include: Should not have lengthy throughputs, or possibly blocking task Be aware of Priority Pitfall relationship when assigning order or priority It is important to perform minimal task processing, as quickly as possible On Microcontrollers typical applications are built as a Hybrid of Cooperative and Pre-Emptive Scheduling Through combination, Cooperative Scheduling can improve performance while Pre-Emptive can expect a resource cost reduction It allows for actions or task, to be Pre-emptively requested, but Cooperatively processed This will gives the system more control on deciding task prioritization Now lets get into Lab 2 The objectives of Lab 2 are to: Redesign Lab 1 code using Cooperative and Pre-Emptive Scheduling Add simple Cooperative Time based task scheduler using a Timer peripheral managed Pre-Emptively Redesign ISR operations to minimalize jitter injection and improve system latency In conclusion of Lab 2: Demonstrated a simple Scheduler with basic Pre-Emptive Task Processing, and Cooperative Execution Explored the modified and expanded inefficient code which had created concurrency issues in Lab 1; but now allows for improved system performance Allowed observation of Mutex, Semaphore and conditional variable implementations which allowed for responsive application behavior improving system performance RTOS’s have been referred to a few times through this lecture Lets now discuss them in a little more detail An Real Time Operating System (RTOS) handles multi-tasking scheduling for all processes required by the application It will Maintain time deadlines, and functionality under real time constraints as defined by the developer They will abstract possible development complexity With an RTOS the user will have task written which are required by the Application Program They put this task into the RTOS, which will manage the hardware requirements for the task Through this design it easy to development large complex system where the User may not have intricate knowledge of the drivers or API’s being used The RTOS will then figured out how best to distribute the Core Processors usage It will manage all required Context sensitive data As well as variable interactions between task In other words it does all the things discussed previously in this lecture The RTOS simply abstracts that away from the USER, and expects only the Task functions which are required for the application Depending upon the RTOS it may or may no leverage full usage of compilers, or may only be supported by a limited number of compilers Because of this constraint an RTOS will be broader (scaled out) in its design

This means it may require larger resources and perform less efficient then a Developer created Scheduler with a more narrow (scaled up) design The positives of RTOS Architectures are: They will handle Resource and Memory Management It will take care of Task Scheduling, abstracting those complexity from the user The Negatives are: They will Require large or dedicated amount of resources, even if some features are never used RTOS cost is upfront and automatically consumes some component resources All function task managed by the RTOS still need to be coded, and also consume resources Setup and configuration of RTOS’s are still dependent upon a User’s Knowledge of its commands, usage and architecture Finally lets talk about some basic Scheduling Techniques which support many usages in system design and operation The most standard way to Scheduler Task for execution is “Sequentially” This is as simple as it looks You execute one task, then the next, until all task have been processed Once completed, you loop back to the top and repeat the cycle The Pros to Sequential Scheduling are: The Code is easy to follow It is linear, making it simple to navigate Execution is in a defined order each time with no variation if not Pre-Empted Some Cons are: Can lead to lengthy TOTAL system throughput More likely to be performing more work then is required if not combined with other techniques Largely effected by large variation in Latency Implementations of the Sequential technique are used in the Lab for the: Scheduler Ticker Dequeuer The Push Button in particular should be referenced as it is called often and thus uses a “Pass Through” method of programming Pass Through is a good method when task are called often and must be processed with very fast throughputs It was described during Lab 2, in the Description section The “Round Robin” processing techniques uses “Sequential” processing in a modified way Where as Sequential would do Task 1 and Task 2 and Task 3 and so on Round Robin will do Task 1, then Task 2, then Task 3, and so on Sequential does them all in one pass of the while (1) loop Round Robin does one for each pass But “Sequential” rotates through its task The Pros to Sequential Scheduling are: Code automatically rotates between task execution Easy to manage and expand for additional task Reduces TOTAL throughput of system by dividing responsibilities Allows of Module function development which is easy to architect Some Cons are: Latency of task depends on position being processed when event arrives Use of function pointers makes code more complex Cannot guarantee instant or minimal latency response time Implementations of a “Round Robin” technique in the Lab is: The Low Priority Task Handler Basic Priority is typically a “Mutex” decision of some kind This Task… Or That Task… What ever the logic is for the choice really just depend on design Lets say Task 2 is more important then Task 1 Task 2 only requires processing whenever an event occurs; but it has a long throughput Task 1 needs to be processed more often but is less vital to stability

It has a short throughput In this example, we can use a Flag If false; Task 1 is executed If true; Task 2 is executed This way Task 1 is executed the majority of the time But whenever the event occurs, Task 2 is prioritized The Pros to Sequential Scheduling are: Can prevent unrequired work from being performed with minimal overhead Easily alters system behavior based on single Boolean variable state Simplest form of Synchronization Some Cons are: Must ensure the variable state is retained and guards corruption Can block or starve a task Imagine if Task 2 had constantly triggered Task 1 would be blocked from processing, and would starve Remember that it required to be called often Can cause wide variance to TOTAL throughput or Latency Task 2 was much longer then Task 1 Implementations of a “Basic Priority” technique in the Lab is: The Basic Priority Handler which had a High Priority task, or called the Low Priority Round Robin handler Advance Priority is a “Semaphore”, where Basic was a “Mutex” It is just additional logical checks In this slides example, the FLAG has highest priority, ignoring Task 1’s priority over Task 2 The Pros to “Advanced Priority” techniques are: Additional logic checks allow for more options for the application Easily expandable when structured correctly Allows for better system control Increase the amount of information about the system allowing for more detailed synchronization decisions Some Cons are: It can make the code more complicated There are more chances for bugs to sneak it Writing well isolated, abstracted code makes finding these bugs easier It will require the Scheduler to have better oversight over the system A good design specification is suggested More variables must be maintained and stored which increase resource cost Implementations of a “Advanced Priority” technique in the Lab is: LED Alarm State The “Staged” technique is best used when an full Task has too lengthy throughput The Task is broken down into separate execution stages Each pass through the task will process a single stage and release In this way the stages are processed using the “Round Robin” technique The Pros to a “Staged Execution” techniques are: Breaks up lengthy task into small manageable parts Reduces TOTAL system throughput length Useful to create State machines to manage code which require a delay This can avoid the blocking wait, and free the system for another task Easier to debug problems as a result of more isolate code

Some Cons are: Increases TOTAL throughput of Staged Task being processed from Start  Finish Results in Longer Latency for the Staged Task Requires dedicated variable to manage state Must protect against corrupted state Implementations of a “Staged” technique in the Lab is: Diagnostic Printout in Lab 3 The “Scheduled” Technique is perhaps the most useful for multi-task management It works by having a dedicated peripheral Timer used to maintain a Timer Tick value This value is used to build Scheduled Task which are processed Cooperatively Scheduled Task can be processed using many techniques, depending upon system requirements In this implementation, the Scheduled events are processed “Sequentially” So the (1) Second task will accrue the Latency cost of the 10 and 100 mSec Task execution This means the occur (9) times On the 10th, the 100 mSec Task will also be proccessed On the 100th 10 mSec event, the 100 mSec event will be processed and then the 1 S event The Pros to the “Scheduled” technique are: Better control over timed, or periodic events Typically easy to read and follow code logic Isolates and abstracts code work to predictable intervals Ideal for managing performance of a Cooperative System Easy to expand for future features, and creates very flexible applications Some Cons are: Requires a dedicated & accurate Timer or Clock source Resources required to manage, and maintain Ticker for Scheduler Timer/Clock should occupy Highest Priority Pre-Emption Interrupt Will Inject Jitter onto Cooperative System processing Ticker maintenance For Resource Constrained systems, a strong application scope will be required for most efficient system architecture Implementations of a “Staged” technique in the Lab is: Diagnostic Printout in Lab 3 “Queuing” is a Technique where triggered events are acknowledged but stored later for execution When an event occurs, the corresponding Task responsibility is “EnQueued” This adds it to the “Queue” The Queue is filled with Triggered Task It could fill from bottom up, or top down Depends on how it is intended to be processed Also if there is to be any type of prioritization This can be done until the Queue is Full The Pros to the “Queue” technique are: Allows for dynamic list population of defined system events Can be sorted or re-arranged to meet system priority requirements Easy to modify Queue references or method of EnQueueing Some Cons are: Population order of list can be non-deterministic May be complex or difficult to navigate without knowledge of system architecture Must be managed to prevent overflow

Event Trigging interval vs processing throughput relationship must be managed Meaning it shouldn’t fill faster then it can be emptied Task added to the list must always be handled to prevent Starvation May be more difficult to read code “at a glance” Implementations of a “Queue” technique in the Lab is: Diagnostic Printout in Lab 3 If “Queueing” is done then a “DeQueue” Technique must also be used along with it Like the “Queue” the design can be adjusted to match the need For this example, the DeQueue is a 2nd buffer which directly copies out from the “Queue” once filled By making them separate When the “Queue” becomes free, more task can be produced; “EnQueued” Later to be consumed; “DeQueued” Again like the “Queue”, the technique used to “DeQueue” depends on the system requirement For this example, when the “DeQueue” buffer becomes full, it triggers “Sequential” processing of all task The Pros to the “DeQueue” technique are: Processing of Queued Task can be done using variety of Techniques Requires little modification to alter method of processing “DeQueuer” Advanced logic decisions can easily be added, removed or expanded without effecting Queue Process Some Cons are: Must be able to empty faster then filled Must be able to process all Task Implementations of a “DeQueue” technique in the Lab is: Queue Executor in Lab 3 Lets walk through a quick Scheduler Example which combines the use of these Techniques Call it a “Round Robin Scheduler” The rule is that each task may only be processed for a length of “Quantum Time”; in this case a value of 2 There are (4) task managed by the scheduler Each has its own “Burst Time” representing the Total Throughput required to reach task completion Task 1 has a value of (2) Task 2 a value of (4) Task 3 a value of (3) And Task 4 a value of (4) Since Task 2, 3, and 4 all have “Burst Times” larger then the allowed “Quantum Time” This means they must be broken into stages Task 2 has (2) stages, each with its own “Stage Burst Time” of (2) Task 3 also has (2) stages, but one “Stage Burst Time” is (2) while the other is (1) Task 4 has (3) stages, Staged Burst Times: 1st @ (2), 2nd @ (2), and 3rd @ (1) This “Round Robin” is executed “Sequentially” In addition it uses a “Queue” which make sure all (4) task complete before the “Round Robin” is reloaded and starts over The “Queue” always loads the same, Task 1 -> 4 The Result is the Scheduler will process as follows: All of Task 1 Stage 1, Task 2 Stage 1, Task 3 Stage 1, Task 4 Stage 2, Task 2 Stage 2, Task 3 Stage 2, Task 4 Stage 3, Task 4 Repeat, etc… Now lets get into the code of Lab 3

The objectives of Lab 3 are to: Expand Lab 2 with additional process scheduling techniques Supply code examples and implementation of the discussed techniques Develop an expansive Application using a Hybrid of Cooperative and Pre-Emptive process scheduling In conclusion of Lab 3: Created a simple but expansive demonstration application using multiple scheduling techniques Implemented a Semaphore design to ISR to mitigate impact of Pre-Emptive Task processing on Scheduler Timer value Followed “Pre-Emptively Requested, Cooperatively Processed” principle by moving the majority of code execution to main-line code and out of ISR Encouraged additional learning with challenged Bonus problem And those are all the important fundamental concepts need to write a good scheduler Today we covered: Concurrency – How Race conditions occur, and method of Synchronization to over come them Interrupts – The Hardware and configuration Shadow Register and Context-Switching Parallelism – Used to achieve multi-tasking through Peripherals and CIPs Cooperative and Pre-Emptive – The types of Process Scheduling RTOS Architecture – What they do and require, how they operate, and execute Techniques for Scheduling with PIC® Microcontrollers – How they can be combined to create ideal “Scaled Up” designs