Hello, if you have any need, please feel free to consult us, this is my wechat: wx91due
ECE-013: COMPUTER SYSTEMS AND “C” PROGRAMMING
Lab 7 - Toaster Oven
27 Points
Warning
This lab is much more involved than the previous ones, and will take you more time. You will need to start early and read this lab carefully. Failure to plan is planning to fail.
Introduction
This lab introduces finite state machines as a tool for programming reactive systems.FSMs are commonly used, though they are rarely explicitly described with those words. They are a very powerful technique for organizing the behavior of complex systems (and just as importantly debugging them). FSMs are used, for example, in your microwave, dish washer, thermostat, car, and many others. Mastering software state machines is one of the fundamental skills you will learn in this class—and a skill that will be quite valuable to you in your programming careers.
For this lab you will implement a toaster oven by following the state machine diagram we have provided. This lab builds on the event- driven programming used in the Bounce lab. A proper state machine uses events to trigger state transitions with an idealized instantaneous transition. This is a key idea in embedded systems.
You will need to able to understand and reason about state machines to do well in this lab. Make sure you do the reading before attempting the lab. Use the example bounce FSM provided here to rewrite your Bounce code using a state machine implementation as a test of your understanding. Ask a TA/tutor for help in working through this and you'll understand much better how to implement this lab
Concepts
. const variables
. Timer interrupts
. Free Running Counters
. Event-driven programming
. Finite state machines
Reading
. This lab manual (you need to read this carefully, probably several times)
. CKO – Chapters 5.8– 5.11
. Wikipedia on FSMs: https://en.wikipedia.org/wiki/Finite- state_machine
Required Files:
. Lab07_main.c
. Leds.h
. README.pdf
Lab Files:
. DO NOT EDIT these files:
o BOARD.c/.h – Standard hardware library for CSE13E.
o Oled.h, Ascii.h – These files provides the interface for manipulating the oled Oled. No .c files are needed here, as Lab7SupportLib contains compiled definitions for the functions in Oled.h.
o Buttons.h , ADC.h – Same as the files from the previous lab. However, the definitions in these functions are now implemented in Lab7SupportLib.a.
o Lab7SupportLib.a – This is a “static library” file. It’s similar to a *.o file. It contains the executable code for the functions in Buttons.h and ADC.h. This allows you to use the ADC and Buttons libraries, without having direct access to our code.
. To use Lab7SupportLib.a, right-clicking on the “Libraries” folder in your MPLAB X project panel and selecting "Add Library/Object File.. .".
. Do not create a Buttons.c or ADC.c file!
. Edit these files (these are provided as *_template.c/h files, you must rename them):
o Leds.h – a simple macro file. It serves the same purpose as the Leds_Lab06 library from the previous lab, but implements all “functions”using macros.
. Do not create a Leds.c file!
o Lab07_main.c – This file will contain all of the executable code that you submit for this lab.
Assignment requirements:
In order to master finite state machines, you are going to implement a toaster over on the microcontroller.
In this case, you are going to display the state of the toaster oven graphically on the OLED rather than have you interact with actual heating elements. This lab requires you to implement all the required toaster oven functionality in Lab07_main.c utilizing the provided libraries.
Toaster Oven Functionality:
. The system displays (on the OLED) the heating elements state in a little graphical toaster oven, the cooking mode, the current time (set time or remaining time when on), and the current temperature (except for when in toast mode). Additionally agreater-than sign (>) is used in Bake mode to indicate whether time or temp is configurable through the potentiometer.
o A function called updateOvenOLED() should contain all OLED- altering code. Note that the file Ascii.h contains several special characters that are useful for drawing the oven.
. First, the user will select a mode and configure a cook time and/or temperature:
o The toaster oven has 3 cooking modes, which can be rotated through by pressing BTN3 for < 1s. They are, in order: bake, toast, and broil.
. Bake mode: Both temperature and time are configurable, with temperature defaulting to 350 degrees F and time to 0:01. Switching between temp and time can be done by holding BTN3 for> 1s (defined as a LONG_PRESS).
Whichever is selected has an indicator beside its label (the selector should always default to time when entering this mode). Both top and bottom heating elements are used when cooking in bake mode.
. Toast mode: Only the time can be configured in this mode, and the temperature is not displayed. There is no selector indicator on the display. Only the bottom heating elements come on in toast mode.
. Broil mode: The temperature is fixed at 500 degrees F and only time is configurable in this mode. The temperature is displayed in broil mode. Again, the input selector indicator is not displayed. Only the top heating elements come on in broil mode.
o While in this phase of operation, the user can rotate the potentiometer to adjust the time or temperature. There is a 2-state variable, called the “settings selector”, which determines which setting the pot controls.
. The settings selector is switched by holding BTN3 for >1 second.
. The cooking time is derived from the ADC value obtained from the Adc library by using only the top 8 bits of the ADC reading and adding 1. This results in a range from 0:01 to 4:16 minutes.
. The cooking temperature is obtained from the potentiometer by using only the top 8 bits of the ADC value and adding 300 to it. This allows for temperatures between 300 and 555.
. Once a mode, time, and (if appropriate) temperature are selected, then cooking is started by pressing down on BTN4. This turns on the heating elements on the display (as they're otherwise off) and the LEDs (see below).
o Cooking can be ended early by holding down BTN4 for >1 second. This should reset the toaster to the same cooking mode that it was in before the button press.
. Additionally, if the time/temp selector should return to their settings when baking started. So, if the user selects 1:00 minute, then cooks for 30 seconds, then cancels cooking, the timer should now say 1:00 minute.
o When the toaster oven is on, the 8 LEDs indicate the remaining cook time in a horizontal “progress bar” to complement the text on the OLED. At the start of cooking, all LEDs should be on. After 1/8
of the total time has passed, LD1 will turn off. After another 1/8 of the original cook time, LD2 will turn off, and so on until all LEDs are off at the end.
o After cooking is complete, the system will return to the current mode with the last used settings; the heating elements should be off, the time and temperature reset to the pot value, and the input selector displayed if in bake mode.
. Extra credit: After cooking is complete, the toaster oven enters an “alert” mode, blinking its screen at intervals.
o You will need to use the Oled library’s invert function, and add a state to the state machine.
o Describe your implementation in your README.pdf.
Code requirements:
When implementing the toaster oven functionality, your code should adhere to the following restrictions:
. The template code has two timer ISRs. Keep them very short and simple! The idea is to get back to your main code as soon as possible.
o The 100Hz timer should be used exclusively to check for button events and ADC events. This ensures the system is very responsive to button presses.
o The 5Hz timer:
. Sets a TIMER_TICK event flag
. Increment the freerunning timer.
This timer is never reset, but is instead used as a “global” timer.
This timer is used to determine whether a button press was a long press or a short press, and is used to determine cooking progress.
This is a useful technique when you need to time multiple events using a single timer (discussed below).
. No floating point numbers are allowed in your code. You will be performing integer math to get all required values.
o Floating point operations are much slower than standard arithmetic! While it doesn’t matter much in this lab, it’s important to build good habits now
. Implement all of your Toaster Oven’s behavioral logic with a single state machine, which you should keep in the runOvenSM() function.
o Your state machine should use a single switch statement to check the state variable, and each case should use if()
statements to handle particular events. Don’t forget to break or return!
o You may (of course) use helper functions to keep your state machine clean, but the overall logic must still follow these rules.
. Your toaster should be an entirely event-driven system.
o Your state machine should be called from your main loop (ie, in a while(1) inside of your main()), but it should only run when an event occurs.
o Your main loop should not do anything besides polling the event
flag and calling the state machine when appropriate.
. It should DEFINITELY not call updateOvenOLED() directly.
This function is very slow, and should only be called when necessary.
o This state machine code should NOT be called directly by an interrupt. Interrupts should set event flags that are checked by the state machine loop and passed into runOvenSM().
o Don’t forget to clear your event flags once your state machine is done with them!
. Create a single struct that holds all toaster oven data: oven state (what state the oven state machine is in), temperature, cooking mode, button press time, cooking start time, and input Selector (whether the pot affects time or temp).
o Create a single instance of this struct as a module-level variable.
o Make sure each member variable has comments indicating what they hold and their units, if any.
. Used named constants and types:
o Create a single typedef’d enum to name your states. Use an instance of this type in your module-level struct.
o Use the same technique to name your cook modes and Selector settings.
o All constants must be declared as constants using either #define, enum or const variable. This includes constants like 300 (degrees) or 5 (ticks per second).
. Use safe data practices:
o Any variables created outside of main must be declared static so that they exist only as module level variables.
o Any strings used to specify formatting for (s)printf() should be declared as const to allow for compiler optimizations.
General:
. Format your code to match the style guidelines in the ECE013E_StyleGuidelines document.
. Make sure that your code triggers no errors or warnings when compiling. Compilation errors in libraries will result in no credit for that section, which is nearly all of the points available in this lab!
o Commit often and submit early! That way you can be sure to have a compiling version that you can submit.
. Compilation errors in the main file will result in NO CREDIT. Any compilation warnings will result in two lost points.
Lab Writeup:
Your README.pdf must contain the following items. Spelling and grammar
count as part of your grade so you'll want to proof-read this before submitting. This will follow the same rough outline as a lab report for a regular science class. It should be on the order of three paragraphs with several sentences in each paragraph.
o First you should list your name & the names of colleagues who you have collaborated with.
o In the next section you should provide a summary of the lab in your own words. Highlight what you thought were the important aspects of the lab. If these differ from how the lab manual presents things, make a note of that.
o The subsequent section should describe your approach to the lab. What was your general approach to the lab? Did you read the manual first or what were your first steps? What went wrong as you worked through it? What worked well? How would you approach this lab differently if you were to do it again? How did you work with other students in the class and what did you find helpful/unhelpful?
o The final section should describe the results of you implementing the lab. How did it end up finally? How many hours did you end up spending on it? What did you like about it? What did you dislike? Was this a worthwhile lab? Do you have any suggestions for altering it to make it better? What were the hardest parts of it? Did the points distribution for the grading seem appropriate? Did the lab manual cover the material in enough detail to start you off? Did examples or discussions during class help your understanding this lab or would more teaching on the concepts in this lab help?
Grading
This assignment consists of 27 points, with a 1-point “cushion” .
Note that all components are human-graded (there’s just not much we can automate in this lab!)
. 9 points: Toaster oven functionality
o 3 points: Oven cycles through three selectable modes of operation, and settings can be selected and adjusted as appropriate.
o 2 points: Oven counts down, and resets when complete.
o 3 points: Oven correctly displays the required information on the OLED and LEDs.
o 1 point: Oven cannot get “stuck,” so that the board must be reset.
. 10 points – Followed the specs for code organization, interrupt handling, and output.
o 5 points: Oven is fully event-driven
o 3 points: State machine is implemented correctly
o 1 point: Oven timing is correct.
o 1 point: All data is appropriately scoped.
. 1 point extra credit – Invert the display at 2Hz after cooking completes. . 2 points: Code style
. 6 points: Writeup
. 1 point: Leds.h macros get, set, and initialize LEDs correctly. . -X points:
o NO CREDIT for code with compilation errors
o -2 points: at least 1 compilation warning
o -2 points: for using gotos, extern, or global variables
o Additional deductions at grader discretion
o Additional bonuses at grader discretion, particularly for README.pdf explanations of features that didn’t quite work.
Free Running Counters
In an embedded system, it is often a requirement to know how much time has elapsed between two events. You can see this in everyday use in many everyday electronic items. There are several ways to accomplish this. One is to dedicate a hardware timer to be started on the first event, and stopped when the second occurs. This is very precise, assuming you can spare the hardware timer, and that the longest time elapsed between events is within the range of the timer. However, if you are going to do this for more than a single pair of events, then you will be using up all of your hardware timers on this task alone.
Another method is to use what is called a free running counter: a timer is set to periodically interrupt, and increments the free running counter.7 To find out the time elapsed, copy the value of the free running counter to a variable, startTime, when the first event occurs, and on the second event:
Elapsed Time = current FreeRunning Time – start Time; |
The elapsed time will be in units of timer event ticks. Note that you can do this for as many things you want elapsed time for without using any additional hardware. Because this is all happening in integer 2’s- complement math, a single rollover on the free running counter does not alter the results of the calculation. Two rollovers of the free running counter between first and second event would be required to give you the wrong elapsed time. If your timer is ticking at 5Hz, and you use a 16-bit integer, then you have over 3 ½ hours before you get a wrong elapsed time.
Integer Math
Most embedded systems or microcontrollers do not have a full floating point unit built into their hardware. As such, floating point math must be emulated and is very processor intensive. We can use floats if we must, but try to minimize their use to absolute necessities.
The larger truth is that you don’t often need floating point, but can get by using integer math (or fixed-point math) most of the time. However, using integer math does require some care in the order of operations to make sure you don’t get the wrong results.
For example, let’s say you wanted to convert your ADC reading (an unsigned 10 bit integer) to a percentage. The mathematical formula is straightforward:
ADC % = (AD1(C)023(Rea)ding ) × 100
ADCpercent = (ADCGetValue() / 1023) * 100;
However if you implement it that way, you will always get 0% as a result because the integer divide occurs first (and the result will always be 0). Thus the correct way to implement it would be:
ADCpercent = (ADCGetValue() * 100) / 1023;
You must take care that the initial multiplication can fit into the variable size without overflowing. If it might overflow, then use a “cast” to temporarily increase the size for the calculation:
ADCpercent = ( (uint32_t) ADCGetValue() * 100) / 1023;
Lastly, integer math additions and subtractions work when using 2s
complement math such that you get the right result even when going past the number boundaries. If you are going to divide or multiply by a power of 2, use shifts instead. They are much faster and typically
directly supported by underlying hardware.
Macros
Macros are a very powerful tool that can make code simpler to implement, easier to understand, and faster (not necessarily all three every time!). But with great power, comes great responsibility. The macro system in C (unlike some more modern languages) does not do any input checking and mostly does straight textual substitution. This makes it very easy to run into problems.
The most basic usage for macros are for declaring constants using the #define preprocessor directive like:
#define TRUE 1
Using a constant like this makes your code easier to read, so instead of a number which you may not understand, you have a name that hopefully hints to its function. The other advantage to this is that, unlike using the const modifier, no memory space is used to store this number; it is merely substituted in for the name wherever it appears in the source code.
Using macro constants is the most basic usage of macros. A more advanced use is for writing multiple-statement blocks. These aren't functions in the regular sense, they don't return anything, but can take arguments. What they are is really a smarter way to do a straight text replacement, basically templated textual replacement.
For example, here's a macro that just divides the PR2 by a number:
#define DIVIDE_IT(num) (PR2 = PR2 / (num))
So if you call it like follows:
DIVIDE_IT(5);
Everything will compile properly and the resulting code would be:
(PR2 = PR2 / (5));
So whatever text is passed in as the argument is substituted for "num" in the output text. Also note that we included parenthesis around num. This is to make sure the following works correctly: DIVIDE_IT(5 + 12*x); If we didn't include the parenthesis, the text output would be:
(PR2 = PR2 / 5 + 12*x);
, which is not what we intended. We also didn't include a semicolon in the macro because we want the user to add one as they normally do after regular statements in C and for the user to understand that they can treat the macro as a single statement, much like a conventional C function call. But with multiple statements in a macro we have to do something a little extra to keep that functionality. We use curly braces to ensure that the compiler treats this code as a single block (the "\" are to continue the macro on the next line):
#define INIT_ECAN(bsize) { \
CB_Init(&ecan1TxCBuffer, txDataArray, bsize); \
CB_Init(&ecan1RxCBuffer, rxDataArray, bsize); \
}
So with that macro written using a {} block, we can call it like a single function call:
INIT_ECAN(10);
And now the semi-colon works properly and the code is grouped into a single statement.
To help debug macro-related issues, MPLAB X gives you a very powerful tool. Right click anywhere on your code and select Navigate -
> View Macro Expansion. The window that opens at the bottom shows the same portion of the code that you have shown in the main coding window, but with all the macros expanded and inlined.